mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
fix(nuxt): use key to force server component re-rendering (#19911)
This commit is contained in:
parent
8b7df05ff0
commit
e8e01bac13
@ -1,4 +1,5 @@
|
|||||||
import { computed, createStaticVNode, defineComponent, getCurrentInstance, ref, watch } from 'vue'
|
import type { RendererNode } from 'vue'
|
||||||
|
import { computed, createStaticVNode, defineComponent, getCurrentInstance, h, ref, watch } from 'vue'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendHeader } from 'h3'
|
import { appendHeader } from 'h3'
|
||||||
@ -33,7 +34,7 @@ export default defineComponent({
|
|||||||
const instance = getCurrentInstance()!
|
const instance = getCurrentInstance()!
|
||||||
const event = useRequestEvent()
|
const event = useRequestEvent()
|
||||||
|
|
||||||
const html = ref<string>(process.client ? instance.vnode.el?.outerHTML ?? '<div></div>' : '<div></div>')
|
const html = ref<string>(process.client ? getFragmentHTML(instance?.vnode?.el).join('') ?? '<div></div>' : '<div></div>')
|
||||||
const cHead = ref<Record<'link' | 'style', Array<Record<string, string>>>>({ link: [], style: [] })
|
const cHead = ref<Record<'link' | 'style', Array<Record<string, string>>>>({ link: [], style: [] })
|
||||||
useHead(cHead)
|
useHead(cHead)
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const key = ref(0)
|
||||||
async function fetchComponent () {
|
async function fetchComponent () {
|
||||||
nuxtApp[pKey] = nuxtApp[pKey] || {}
|
nuxtApp[pKey] = nuxtApp[pKey] || {}
|
||||||
if (!nuxtApp[pKey][hashId.value]) {
|
if (!nuxtApp[pKey][hashId.value]) {
|
||||||
@ -63,6 +64,7 @@ export default defineComponent({
|
|||||||
cHead.value.link = res.head.link
|
cHead.value.link = res.head.link
|
||||||
cHead.value.style = res.head.style
|
cHead.value.style = res.head.style
|
||||||
html.value = res.html
|
html.value = res.html
|
||||||
|
key.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
@ -72,7 +74,40 @@ export default defineComponent({
|
|||||||
if (process.server || !nuxtApp.isHydrating) {
|
if (process.server || !nuxtApp.isHydrating) {
|
||||||
await fetchComponent()
|
await fetchComponent()
|
||||||
}
|
}
|
||||||
|
return () => h((_, { slots }) => slots.default?.(), { key: key.value }, {
|
||||||
return () => createStaticVNode(html.value, 1)
|
default: () => [createStaticVNode(html.value, 1)]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO refactor with https://github.com/nuxt/nuxt/pull/19231
|
||||||
|
function getFragmentHTML (element: RendererNode | null) {
|
||||||
|
if (element) {
|
||||||
|
if (element.nodeName === '#comment' && element.nodeValue === '[') {
|
||||||
|
return getFragmentChildren(element)
|
||||||
|
}
|
||||||
|
return [element.outerHTML]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFragmentChildren (element: RendererNode | null, blocks: string[] = []) {
|
||||||
|
if (element && element.nodeName) {
|
||||||
|
if (isEndFragment(element)) {
|
||||||
|
return blocks
|
||||||
|
} else if (!isStartFragment(element)) {
|
||||||
|
blocks.push(element.outerHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFragmentChildren(element.nextSibling, blocks)
|
||||||
|
}
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartFragment (element: RendererNode) {
|
||||||
|
return element.nodeName === '#comment' && element.nodeValue === '['
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEndFragment (element: RendererNode) {
|
||||||
|
return element.nodeName === '#comment' && element.nodeValue === ']'
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { computed, createStaticVNode, defineComponent, h, watch } from 'vue'
|
import { Fragment, computed, createStaticVNode, createVNode, defineComponent, h, ref, watch } from 'vue'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendHeader } from 'h3'
|
import { appendHeader } from 'h3'
|
||||||
@ -42,6 +42,7 @@ const NuxtServerComponent = defineComponent({
|
|||||||
},
|
},
|
||||||
async setup (props) {
|
async setup (props) {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
const key = ref(0)
|
||||||
const hashId = computed(() => hash([props.name, props.props, props.context]))
|
const hashId = computed(() => hash([props.name, props.props, props.context]))
|
||||||
|
|
||||||
const event = useRequestEvent()
|
const event = useRequestEvent()
|
||||||
@ -92,11 +93,14 @@ const NuxtServerComponent = defineComponent({
|
|||||||
useHead(() => res.data.value!.head)
|
useHead(() => res.data.value!.head)
|
||||||
|
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
watch(props, debounce(() => res.execute(), 100))
|
watch(props, debounce(async () => {
|
||||||
|
await res.execute()
|
||||||
|
key.value++
|
||||||
|
}, 100))
|
||||||
}
|
}
|
||||||
|
|
||||||
await res
|
await res
|
||||||
|
|
||||||
return () => createStaticVNode(res.data.value!.html, 1)
|
return () => createVNode(Fragment, { key: key.value }, [createStaticVNode(res.data.value!.html, 1)])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -337,6 +337,23 @@ describe('pages', () => {
|
|||||||
|
|
||||||
await page.close()
|
await page.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('/islands', async () => {
|
||||||
|
const page = await createPage('/islands')
|
||||||
|
await page.waitForLoadState('networkidle')
|
||||||
|
await page.locator('#increase-pure-component').click()
|
||||||
|
await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200)
|
||||||
|
await page.waitForLoadState('networkidle')
|
||||||
|
expect(await page.locator('.box').innerHTML()).toContain('"number": 101,')
|
||||||
|
await page.locator('#count-async-server-long-async').click()
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200),
|
||||||
|
page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200)
|
||||||
|
])
|
||||||
|
await page.waitForLoadState('networkidle')
|
||||||
|
expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1'))
|
||||||
|
expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('rich payloads', () => {
|
describe('rich payloads', () => {
|
||||||
@ -1109,7 +1126,7 @@ describe('component islands', () => {
|
|||||||
const result: NuxtIslandResponse = await $fetch('/__nuxt_island/RouteComponent?url=/foo')
|
const result: NuxtIslandResponse = await $fetch('/__nuxt_island/RouteComponent?url=/foo')
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
result.head.link = result.head.link.filter(l => !l.href.includes('@nuxt+ui-templates'))
|
result.head.link = result.head.link.filter(l => !l.href.includes('@nuxt+ui-templates') && (l.href.startsWith('_nuxt/components/islands/') && l.href.includes('_nuxt/components/islands/RouteComponent')))
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
@ -1132,7 +1149,7 @@ describe('component islands', () => {
|
|||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
result.head.link = result.head.link.filter(l => !l.href.includes('@nuxt+ui-templates'))
|
result.head.link = result.head.link.filter(l => !l.href.includes('@nuxt+ui-templates') && (l.href.startsWith('_nuxt/components/islands/') && l.href.includes('_nuxt/components/islands/LongAsyncComponent')))
|
||||||
}
|
}
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@ -1153,7 +1170,7 @@ describe('component islands', () => {
|
|||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
result.head.link = result.head.link.filter(l => !l.href.includes('@nuxt+ui-templates'))
|
result.head.link = result.head.link.filter(l => !l.href.includes('@nuxt+ui-templates') && (l.href.startsWith('_nuxt/components/islands/') && l.href.includes('_nuxt/components/islands/AsyncServerComponent')))
|
||||||
}
|
}
|
||||||
expect(result).toMatchInlineSnapshot(`
|
expect(result).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
6
test/fixtures/basic/pages/islands.vue
vendored
6
test/fixtures/basic/pages/islands.vue
vendored
@ -18,7 +18,7 @@ const count = ref(0)
|
|||||||
<NuxtIsland name="PureComponent" :props="islandProps" />
|
<NuxtIsland name="PureComponent" :props="islandProps" />
|
||||||
<NuxtIsland name="PureComponent" :props="islandProps" />
|
<NuxtIsland name="PureComponent" :props="islandProps" />
|
||||||
</div>
|
</div>
|
||||||
<button @click="islandProps.number++">
|
<button id="increase-pure-component" @click="islandProps.number++">
|
||||||
Increase
|
Increase
|
||||||
</button>
|
</button>
|
||||||
<hr>
|
<hr>
|
||||||
@ -26,7 +26,7 @@ const count = ref(0)
|
|||||||
<div v-if="routeIslandVisible" class="box">
|
<div v-if="routeIslandVisible" class="box">
|
||||||
<NuxtIsland name="RouteComponent" :context="{ url: '/test' }" />
|
<NuxtIsland name="RouteComponent" :context="{ url: '/test' }" />
|
||||||
</div>
|
</div>
|
||||||
<button v-else @click="routeIslandVisible = true">
|
<button v-else id="show-route" @click="routeIslandVisible = true">
|
||||||
Show
|
Show
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ const count = ref(0)
|
|||||||
<div>
|
<div>
|
||||||
Async island component (20ms):
|
Async island component (20ms):
|
||||||
<NuxtIsland name="LongAsyncComponent" :props="{ count }" />
|
<NuxtIsland name="LongAsyncComponent" :props="{ count }" />
|
||||||
<button @click="count++">
|
<button id="count-async-server-long-async" @click="count++">
|
||||||
add +1 to count
|
add +1 to count
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user