mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 07:05:11 +00:00
fix(nuxt): allow islands to manipulate head client-side (#29186)
This commit is contained in:
parent
1a659b326a
commit
ca65f150b3
@ -1,9 +1,9 @@
|
|||||||
import type { Component, PropType, VNode } from 'vue'
|
import type { Component, PropType, VNode } from 'vue'
|
||||||
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendResponseHeader } from 'h3'
|
import { appendResponseHeader } from 'h3'
|
||||||
import { injectHead } from '@unhead/vue'
|
import { type ActiveHeadEntry, type Head, injectHead } from '@unhead/vue'
|
||||||
import { randomUUID } from 'uncrypto'
|
import { randomUUID } from 'uncrypto'
|
||||||
import { joinURL, withQuery } from 'ufo'
|
import { joinURL, withQuery } from 'ufo'
|
||||||
import type { FetchResponse } from 'ofetch'
|
import type { FetchResponse } from 'ofetch'
|
||||||
@ -90,11 +90,13 @@ export default defineComponent({
|
|||||||
const instance = getCurrentInstance()!
|
const instance = getCurrentInstance()!
|
||||||
const event = useRequestEvent()
|
const event = useRequestEvent()
|
||||||
|
|
||||||
|
let activeHead: ActiveHeadEntry<Head>
|
||||||
|
|
||||||
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
||||||
const eventFetch = import.meta.server ? event!.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
const eventFetch = import.meta.server ? event!.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
||||||
const mounted = ref(false)
|
const mounted = ref(false)
|
||||||
onMounted(() => { mounted.value = true; teleportKey.value++ })
|
onMounted(() => { mounted.value = true; teleportKey.value++ })
|
||||||
|
onBeforeUnmount(() => { if (activeHead) { activeHead.dispose() } })
|
||||||
function setPayload (key: string, result: NuxtIslandResponse) {
|
function setPayload (key: string, result: NuxtIslandResponse) {
|
||||||
const toRevive: Partial<NuxtIslandResponse> = {}
|
const toRevive: Partial<NuxtIslandResponse> = {}
|
||||||
if (result.props) { toRevive.props = result.props }
|
if (result.props) { toRevive.props = result.props }
|
||||||
@ -215,6 +217,14 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res?.head) {
|
||||||
|
if (activeHead) {
|
||||||
|
activeHead.patch(res.head)
|
||||||
|
} else {
|
||||||
|
activeHead = head.push(res.head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
// must await next tick for Teleport to work correctly with static node re-rendering
|
// must await next tick for Teleport to work correctly with static node re-rendering
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -250,14 +260,6 @@ export default defineComponent({
|
|||||||
await loadComponents(props.source, payloads.components)
|
await loadComponents(props.source, payloads.components)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.server || nuxtApp.isHydrating) {
|
|
||||||
// re-push head into active head instance
|
|
||||||
const responseHead = (nuxtApp.payload.data[`${props.name}_${hashId.value}`] as NuxtIslandResponse)?.head
|
|
||||||
if (responseHead) {
|
|
||||||
head.push(responseHead)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (_ctx: any, _cache: any) => {
|
return (_ctx: any, _cache: any) => {
|
||||||
if (!html.value || error.value) {
|
if (!html.value || error.value) {
|
||||||
return [slots.fallback?.({ error: error.value }) ?? createVNode('div')]
|
return [slots.fallback?.({ error: error.value }) ?? createVNode('div')]
|
||||||
|
@ -1984,6 +1984,15 @@ describe('server components/islands', () => {
|
|||||||
expect(html).toContain('<meta name="author" content="Nuxt">')
|
expect(html).toContain('<meta name="author" content="Nuxt">')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('/server-page - client side navigation', async () => {
|
||||||
|
const { page } = await renderPage('/')
|
||||||
|
await page.getByText('to server page').click()
|
||||||
|
await page.waitForLoadState('networkidle')
|
||||||
|
|
||||||
|
expect(await page.innerHTML('head')).toContain('<meta name="author" content="Nuxt">')
|
||||||
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user