mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 21:25:11 +00:00
feat(nuxt): allow 'lazy' (non-blocking) server components (#21918)
This commit is contained in:
parent
0991e885fd
commit
5926bbeff8
@ -29,6 +29,7 @@ export default defineComponent({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
lazy: Boolean,
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => undefined
|
||||
@ -66,7 +67,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const ssrHTML = ref('<div></div>')
|
||||
const ssrHTML = ref<string>('')
|
||||
if (process.client) {
|
||||
const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null).join('')
|
||||
if (renderedHTML && nuxtApp.isHydrating) {
|
||||
@ -79,7 +80,7 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
}
|
||||
ssrHTML.value = renderedHTML ?? '<div></div>'
|
||||
ssrHTML.value = renderedHTML
|
||||
}
|
||||
const slotProps = computed(() => getSlotProps(ssrHTML.value))
|
||||
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID())
|
||||
@ -165,18 +166,19 @@ export default defineComponent({
|
||||
watch(props, debounce(() => fetchComponent(), 100))
|
||||
}
|
||||
|
||||
// TODO: allow lazy loading server islands
|
||||
if (process.server || !nuxtApp.isHydrating) {
|
||||
if (process.client && !nuxtApp.isHydrating && props.lazy) {
|
||||
fetchComponent()
|
||||
} else if (process.server || !nuxtApp.isHydrating) {
|
||||
await fetchComponent()
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (error.value && slots.fallback) {
|
||||
if ((!html.value || error.value) && slots.fallback) {
|
||||
return [slots.fallback({ error: error.value })]
|
||||
}
|
||||
const nodes = [createVNode(Fragment, {
|
||||
key: key.value
|
||||
}, [h(createStaticVNode(html.value, 1))])]
|
||||
}, [h(createStaticVNode(html.value || '<div></div>', 1))])]
|
||||
if (uid.value && (mounted.value || nuxtApp.isHydrating || process.server)) {
|
||||
for (const slot in slots) {
|
||||
if (availableSlots.value.includes(slot)) {
|
||||
|
@ -5,9 +5,11 @@ export const createServerComponent = (name: string) => {
|
||||
return defineComponent({
|
||||
name,
|
||||
inheritAttrs: false,
|
||||
setup (_props, { attrs, slots }) {
|
||||
props: { lazy: Boolean },
|
||||
setup (props, { attrs, slots }) {
|
||||
return () => h(NuxtIsland, {
|
||||
name,
|
||||
lazy: props.lazy,
|
||||
props: attrs
|
||||
}, slots)
|
||||
}
|
||||
|
@ -1419,6 +1419,43 @@ describe('server components/islands', () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('lazy server components', async () => {
|
||||
const page = await createPage('/server-components/lazy/start')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.getByText('Go to page with lazy server component').click()
|
||||
|
||||
const text = await page.innerText('pre')
|
||||
expect(text).toMatchInlineSnapshot('" End page <pre></pre><section id=\\"fallback\\"> Loading server component </section><section id=\\"no-fallback\\"><div></div></section>"')
|
||||
expect(text).not.toContain('async component that was very long')
|
||||
expect(text).toContain('Loading server component')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared
|
||||
// await page.waitForLoadState('networkidle')
|
||||
// await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
await page.waitForFunction(() => (document.querySelector('#no-fallback') as HTMLElement)?.innerText?.includes('async component'))
|
||||
await page.waitForFunction(() => (document.querySelector('#fallback') as HTMLElement)?.innerText?.includes('async component'))
|
||||
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('non-lazy server components', async () => {
|
||||
const page = await createPage('/server-components/lazy/start')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.getByText('Go to page without lazy server component').click()
|
||||
|
||||
const text = await page.innerText('pre')
|
||||
expect(text).toMatchInlineSnapshot('" End page <pre></pre><section id=\\"fallback\\"><div nuxt-ssr-component-uid=\\"0\\"> This is a .server (20ms) async component that was very long ... <div id=\\"async-server-component-count\\">42</div><div style=\\"display:contents;\\" nuxt-ssr-slot-name=\\"default\\"></div></div></section><section id=\\"no-fallback\\"><div nuxt-ssr-component-uid=\\"1\\"> This is a .server (20ms) async component that was very long ... <div id=\\"async-server-component-count\\">42</div><div style=\\"display:contents;\\" nuxt-ssr-slot-name=\\"default\\"></div></div></section>"')
|
||||
expect(text).toContain('async component that was very long')
|
||||
|
||||
// Wait for all pending micro ticks to be cleared
|
||||
// await page.waitForLoadState('networkidle')
|
||||
// await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10)))
|
||||
await page.waitForFunction(() => (document.querySelector('#no-fallback') as HTMLElement)?.innerText?.includes('async component'))
|
||||
await page.waitForFunction(() => (document.querySelector('#fallback') as HTMLElement)?.innerText?.includes('async component'))
|
||||
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it.skipIf(isDev)('should allow server-only components to set prerender hints', async () => {
|
||||
// @ts-expect-error ssssh! untyped secret property
|
||||
const publicDir = useTestContext().nuxt._nitro.options.output.publicDir
|
||||
|
26
test/fixtures/basic/pages/server-components/lazy/end.vue
vendored
Normal file
26
test/fixtures/basic/pages/server-components/lazy/end.vue
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const page = ref<HTMLDivElement | undefined>()
|
||||
const mountedHTML = ref()
|
||||
onMounted(() => {
|
||||
mountedHTML.value = page.value?.innerHTML
|
||||
})
|
||||
|
||||
const lazy = useRoute().query.lazy === 'true'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="page" class="end-page">
|
||||
End page
|
||||
<pre>{{ mountedHTML }}</pre>
|
||||
<section id="fallback">
|
||||
<AsyncServerComponent :lazy="lazy" :count="42">
|
||||
<template #fallback>
|
||||
Loading server component
|
||||
</template>
|
||||
</AsyncServerComponent>
|
||||
</section>
|
||||
<section id="no-fallback">
|
||||
<AsyncServerComponent :lazy="lazy" :count="42" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
10
test/fixtures/basic/pages/server-components/lazy/start.vue
vendored
Normal file
10
test/fixtures/basic/pages/server-components/lazy/start.vue
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLink to="/server-components/lazy/end?lazy=true">
|
||||
Go to page with lazy server component
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/server-components/lazy/end?lazy=false">
|
||||
Go to page without lazy server component
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user