mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
fix(nuxt): overwrite island payload instead of merging (#25299)
This commit is contained in:
parent
92ba515549
commit
a57b428587
@ -104,12 +104,15 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const payloadSlots: NonNullable<NuxtIslandResponse['slots']> = {}
|
||||
const payloadComponents: NonNullable<NuxtIslandResponse['components']> = {}
|
||||
const payloads: Required<Pick<NuxtIslandResponse, 'slots' | 'components'>> = {
|
||||
slots: {},
|
||||
components: {}
|
||||
}
|
||||
|
||||
|
||||
if (nuxtApp.isHydrating) {
|
||||
Object.assign(payloadSlots, toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.slots ?? {})
|
||||
Object.assign(payloadComponents, toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.components ?? {})
|
||||
payloads.slots = toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.slots ?? {}
|
||||
payloads.components = toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}`])?.components ?? {}
|
||||
}
|
||||
|
||||
const ssrHTML = ref<string>('')
|
||||
@ -125,7 +128,7 @@ export default defineComponent({
|
||||
let html = ssrHTML.value
|
||||
|
||||
if (import.meta.client && !canLoadClientComponent.value) {
|
||||
for (const [key, value] of Object.entries(payloadComponents || {})) {
|
||||
for (const [key, value] of Object.entries(payloads.components || {})) {
|
||||
html = html.replace(new RegExp(` data-island-uid="${uid.value}" data-island-component="${key}"[^>]*>`), (full) => {
|
||||
return full + value.html
|
||||
})
|
||||
@ -134,7 +137,7 @@ export default defineComponent({
|
||||
|
||||
return html.replaceAll(SLOT_FALLBACK_RE, (full, slotName) => {
|
||||
if (!currentSlots.includes(slotName)) {
|
||||
return full + (payloadSlots[slotName]?.fallback || '')
|
||||
return full + (payloads.slots[slotName]?.fallback || '')
|
||||
}
|
||||
return full
|
||||
})
|
||||
@ -186,8 +189,8 @@ export default defineComponent({
|
||||
ssrHTML.value = res.html.replaceAll(DATA_ISLAND_UID_RE, `data-island-uid="${uid.value}"`)
|
||||
key.value++
|
||||
error.value = null
|
||||
Object.assign(payloadSlots, res.slots || {})
|
||||
Object.assign(payloadComponents, res.components || {})
|
||||
payloads.slots = res.slots || {}
|
||||
payloads.components = res.components || {}
|
||||
|
||||
if (selectiveClient && import.meta.client) {
|
||||
if (canLoadClientComponent.value && res.components) {
|
||||
@ -226,7 +229,7 @@ export default defineComponent({
|
||||
} else if (import.meta.server || !nuxtApp.isHydrating || !nuxtApp.payload.serverRendered) {
|
||||
await fetchComponent()
|
||||
} else if (selectiveClient && canLoadClientComponent.value) {
|
||||
await loadComponents(props.source, payloadComponents)
|
||||
await loadComponents(props.source, payloads.components)
|
||||
}
|
||||
|
||||
return (_ctx: any, _cache: any) => {
|
||||
@ -250,12 +253,12 @@ export default defineComponent({
|
||||
teleports.push(createVNode(Teleport,
|
||||
// use different selectors for even and odd teleportKey to force trigger the teleport
|
||||
{ to: import.meta.client ? `${isKeyOdd ? 'div' : ''}[data-island-uid="${uid.value}"][data-island-slot="${slot}"]` : `uid=${uid.value};slot=${slot}` },
|
||||
{ default: () => (payloadSlots[slot].props?.length ? payloadSlots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) })
|
||||
{ default: () => (payloads.slots[slot].props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) })
|
||||
)
|
||||
}
|
||||
}
|
||||
if (import.meta.server) {
|
||||
for (const [id, info] of Object.entries(payloadComponents ?? {})) {
|
||||
for (const [id, info] of Object.entries(payloads.components ?? {})) {
|
||||
const { html } = info
|
||||
teleports.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, {
|
||||
default: () => [createStaticVNode(html, 1)]
|
||||
@ -263,7 +266,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
if (selectiveClient && import.meta.client && canLoadClientComponent.value) {
|
||||
for (const [id, info] of Object.entries(payloadComponents ?? {})) {
|
||||
for (const [id, info] of Object.entries(payloads.components ?? {})) {
|
||||
const { props } = info
|
||||
const component = components!.get(id)!
|
||||
// use different selectors for even and odd teleportKey to force trigger the teleport
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { beforeEach } from 'node:test'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { h, nextTick } from 'vue'
|
||||
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||
@ -22,6 +23,19 @@ vi.mock('vue', async (original) => {
|
||||
}
|
||||
})
|
||||
|
||||
const consoleError = vi.spyOn(console, 'error')
|
||||
const consoleWarn = vi.spyOn(console, 'warn')
|
||||
|
||||
function expectNoConsoleIssue() {
|
||||
expect(consoleError).not.toHaveBeenCalled()
|
||||
expect(consoleWarn).not.toHaveBeenCalled()
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
consoleError.mockClear()
|
||||
consoleWarn.mockClear()
|
||||
})
|
||||
|
||||
describe('runtime server component', () => {
|
||||
it('expect no data-v- attrbutes #23051', () => {
|
||||
// @ts-expect-error mock
|
||||
@ -95,6 +109,96 @@ describe('runtime server component', () => {
|
||||
expect(fetch).toHaveBeenCalledTimes(2)
|
||||
await nextTick()
|
||||
expect(component.html()).toBe('<div>2</div>')
|
||||
vi.mocked(fetch).mockRestore()
|
||||
vi.mocked(fetch).mockReset()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('client components', () => {
|
||||
|
||||
it('expect swapping nuxt-client should not trigger errors #25289', async () => {
|
||||
const mockPath = '/nuxt-client.js'
|
||||
const componentId = 'Client-12345'
|
||||
|
||||
vi.doMock(mockPath, () => ({
|
||||
default: {
|
||||
name: 'ClientComponent',
|
||||
setup() {
|
||||
return () => h('div', 'client component')
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const stubFetch = vi.fn(() => {
|
||||
return {
|
||||
id: '123',
|
||||
html: `<div data-island-uid>hello<div data-island-uid data-island-component="${componentId}"></div></div>`,
|
||||
state: {},
|
||||
head: {
|
||||
link: [],
|
||||
style: []
|
||||
},
|
||||
components: {
|
||||
[componentId]: {
|
||||
html: '<div>fallback</div>',
|
||||
props: {},
|
||||
chunk: mockPath
|
||||
}
|
||||
},
|
||||
json() {
|
||||
return this
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
vi.stubGlobal('fetch', stubFetch)
|
||||
|
||||
const wrapper = await mountSuspended(NuxtIsland, {
|
||||
props: {
|
||||
name: 'NuxtClient',
|
||||
props: {
|
||||
force: true
|
||||
}
|
||||
},
|
||||
attachTo: 'body'
|
||||
})
|
||||
|
||||
expect(fetch).toHaveBeenCalledOnce()
|
||||
|
||||
expect(wrapper.html()).toMatchInlineSnapshot(`
|
||||
"<div data-island-uid="3">hello<div data-island-uid="3" data-island-component="Client-12345">
|
||||
<div>client component</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--teleport start-->
|
||||
<!--teleport end-->"
|
||||
`)
|
||||
|
||||
// @ts-expect-error mock
|
||||
vi.mocked(fetch).mockImplementation(() => ({
|
||||
id: '123',
|
||||
html: `<div data-island-uid>hello<div><div>fallback</div></div></div>`,
|
||||
state: {},
|
||||
head: {
|
||||
link: [],
|
||||
style: []
|
||||
},
|
||||
components: {},
|
||||
json() {
|
||||
return this
|
||||
}
|
||||
}))
|
||||
|
||||
await wrapper.vm.$.exposed!.refresh()
|
||||
await nextTick()
|
||||
expect(wrapper.html()).toMatchInlineSnapshot( `
|
||||
"<div data-island-uid="3">hello<div>
|
||||
<div>fallback</div>
|
||||
</div>
|
||||
</div>"
|
||||
`)
|
||||
|
||||
vi.mocked(fetch).mockReset()
|
||||
expectNoConsoleIssue()
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user