mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +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,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
lazy: Boolean,
|
||||||
props: {
|
props: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => undefined
|
default: () => undefined
|
||||||
@ -66,7 +67,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ssrHTML = ref('<div></div>')
|
const ssrHTML = ref<string>('')
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null).join('')
|
const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null).join('')
|
||||||
if (renderedHTML && nuxtApp.isHydrating) {
|
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 slotProps = computed(() => getSlotProps(ssrHTML.value))
|
||||||
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID())
|
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID())
|
||||||
@ -165,18 +166,19 @@ export default defineComponent({
|
|||||||
watch(props, debounce(() => fetchComponent(), 100))
|
watch(props, debounce(() => fetchComponent(), 100))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: allow lazy loading server islands
|
if (process.client && !nuxtApp.isHydrating && props.lazy) {
|
||||||
if (process.server || !nuxtApp.isHydrating) {
|
fetchComponent()
|
||||||
|
} else if (process.server || !nuxtApp.isHydrating) {
|
||||||
await fetchComponent()
|
await fetchComponent()
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (error.value && slots.fallback) {
|
if ((!html.value || error.value) && slots.fallback) {
|
||||||
return [slots.fallback({ error: error.value })]
|
return [slots.fallback({ error: error.value })]
|
||||||
}
|
}
|
||||||
const nodes = [createVNode(Fragment, {
|
const nodes = [createVNode(Fragment, {
|
||||||
key: key.value
|
key: key.value
|
||||||
}, [h(createStaticVNode(html.value, 1))])]
|
}, [h(createStaticVNode(html.value || '<div></div>', 1))])]
|
||||||
if (uid.value && (mounted.value || nuxtApp.isHydrating || process.server)) {
|
if (uid.value && (mounted.value || nuxtApp.isHydrating || process.server)) {
|
||||||
for (const slot in slots) {
|
for (const slot in slots) {
|
||||||
if (availableSlots.value.includes(slot)) {
|
if (availableSlots.value.includes(slot)) {
|
||||||
|
@ -5,9 +5,11 @@ export const createServerComponent = (name: string) => {
|
|||||||
return defineComponent({
|
return defineComponent({
|
||||||
name,
|
name,
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
setup (_props, { attrs, slots }) {
|
props: { lazy: Boolean },
|
||||||
|
setup (props, { attrs, slots }) {
|
||||||
return () => h(NuxtIsland, {
|
return () => h(NuxtIsland, {
|
||||||
name,
|
name,
|
||||||
|
lazy: props.lazy,
|
||||||
props: attrs
|
props: attrs
|
||||||
}, slots)
|
}, slots)
|
||||||
}
|
}
|
||||||
|
@ -1419,6 +1419,43 @@ describe('server components/islands', () => {
|
|||||||
await page.close()
|
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 () => {
|
it.skipIf(isDev)('should allow server-only components to set prerender hints', async () => {
|
||||||
// @ts-expect-error ssssh! untyped secret property
|
// @ts-expect-error ssssh! untyped secret property
|
||||||
const publicDir = useTestContext().nuxt._nitro.options.output.publicDir
|
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