mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
fix(nuxt): revert back to object syntax for island head (#28656)
This commit is contained in:
parent
889b6a891c
commit
a0124712f3
@ -248,9 +248,10 @@ export default defineComponent({
|
||||
|
||||
if (import.meta.server || nuxtApp.isHydrating) {
|
||||
// re-push head into active head instance
|
||||
(nuxtApp.payload.data[`${props.name}_${hashId.value}`] as NuxtIslandResponse)?.head?.forEach((h) => {
|
||||
head.push(h)
|
||||
})
|
||||
const responseHead = (nuxtApp.payload.data[`${props.name}_${hashId.value}`] as NuxtIslandResponse)?.head
|
||||
if (responseHead) {
|
||||
head.push(responseHead)
|
||||
}
|
||||
}
|
||||
|
||||
return (_ctx: any, _cache: any) => {
|
||||
|
@ -79,7 +79,7 @@ export interface NuxtIslandContext {
|
||||
export interface NuxtIslandResponse {
|
||||
id?: string
|
||||
html: string
|
||||
head: Head[]
|
||||
head: Head
|
||||
props?: Record<string, Record<string, any>>
|
||||
components?: Record<string, NuxtIslandClientResponse>
|
||||
slots?: Record<string, NuxtIslandSlotResponse>
|
||||
@ -479,9 +479,24 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
|
||||
// Response for component islands
|
||||
if (isRenderingIsland && islandContext) {
|
||||
const islandHead: Head = {}
|
||||
for (const entry of head.headEntries()) {
|
||||
for (const [key, value] of Object.entries(resolveUnrefHeadInput(entry.input) as Head)) {
|
||||
const currentValue = islandHead[key as keyof Head]
|
||||
if (Array.isArray(currentValue)) {
|
||||
currentValue.push(...value)
|
||||
}
|
||||
islandHead[key as keyof Head] = value
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove for v4
|
||||
islandHead.link = islandHead.link || []
|
||||
islandHead.style = islandHead.style || []
|
||||
|
||||
const islandResponse: NuxtIslandResponse = {
|
||||
id: islandContext.id,
|
||||
head: (head.headEntries().map(h => resolveUnrefHeadInput(h.input) as Head)),
|
||||
head: islandHead,
|
||||
html: getServerComponentHTML(htmlContext.body),
|
||||
components: getClientIslandResponse(ssrContext),
|
||||
slots: getSlotIslandResponse(ssrContext),
|
||||
|
@ -8,7 +8,7 @@ import { $fetch, createPage, fetch, isDev, setup, startServer, url, useTestConte
|
||||
import { $fetchComponent } from '@nuxt/test-utils/experimental'
|
||||
|
||||
import { resolveUnrefHeadInput } from '@unhead/vue'
|
||||
import { expectNoClientErrors, expectWithPolling, gotoPath, isRenderingJson, parseData, parsePayload, renderPage, resolveHead } from './utils'
|
||||
import { expectNoClientErrors, expectWithPolling, gotoPath, isRenderingJson, parseData, parsePayload, renderPage } from './utils'
|
||||
|
||||
import type { NuxtIslandResponse } from '#app'
|
||||
|
||||
@ -2148,15 +2148,15 @@ describe('component islands', () => {
|
||||
|
||||
result.html = result.html.replace(/ data-island-uid="[^"]*"/g, '')
|
||||
if (isDev()) {
|
||||
result.head = resolveHead(result.head).map(h => ({
|
||||
...h,
|
||||
link: h.link?.filter(l => typeof l.href !== 'string' || (!l.href.includes('_nuxt/components/islands/RouteComponent') && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */)),
|
||||
})).filter(h => Object.values(h).some(h => !Array.isArray(h) || h.length))
|
||||
result.head.link = result.head.link?.filter(l => typeof l.href !== 'string' || (!l.href.includes('_nuxt/components/islands/RouteComponent') && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */))
|
||||
}
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"head": [],
|
||||
"head": {
|
||||
"link": [],
|
||||
"style": [],
|
||||
},
|
||||
"html": "<pre data-island-uid> Route: /foo
|
||||
</pre>",
|
||||
}
|
||||
@ -2170,15 +2170,15 @@ describe('component islands', () => {
|
||||
}),
|
||||
}))
|
||||
if (isDev()) {
|
||||
result.head = resolveHead(result.head).map(h => ({
|
||||
...h,
|
||||
link: h.link?.filter(l => typeof l.href !== 'string' || (!l.href.includes('_nuxt/components/islands/LongAsyncComponent') && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */)),
|
||||
})).filter(h => Object.values(h).some(h => !Array.isArray(h) || h.length))
|
||||
result.head.link = result.head.link?.filter(l => typeof l.href !== 'string' || (!l.href.includes('_nuxt/components/islands/LongAsyncComponent') && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */))
|
||||
}
|
||||
result.html = result.html.replaceAll(/ (data-island-uid|data-island-component)="([^"]*)"/g, '')
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"head": [],
|
||||
"head": {
|
||||
"link": [],
|
||||
"style": [],
|
||||
},
|
||||
"html": "<div data-island-uid><div> count is above 2 </div><!--[--><div style="display: contents;" data-island-uid data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--> that was very long ... <div id="long-async-component-count">3</div> <!--[--><div style="display: contents;" data-island-uid data-island-slot="test"><!--teleport start--><!--teleport end--></div><!--]--><p>hello world !!!</p><!--[--><div style="display: contents;" data-island-uid data-island-slot="hello"><!--teleport start--><!--teleport end--></div><!--teleport start--><!--teleport end--><!--]--><!--[--><div style="display: contents;" data-island-uid data-island-slot="fallback"><!--teleport start--><!--teleport end--></div><!--teleport start--><!--teleport end--><!--]--></div>",
|
||||
"slots": {
|
||||
"default": {
|
||||
@ -2228,10 +2228,7 @@ describe('component islands', () => {
|
||||
}),
|
||||
}))
|
||||
if (isDev()) {
|
||||
result.head = result.head.map(h => ({
|
||||
...h,
|
||||
link: h.link?.filter(l => typeof l.href === 'string' && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */ && (!l.href.startsWith('_nuxt/components/islands/') || l.href.includes('AsyncServerComponent'))),
|
||||
})).filter(h => Object.values(h).some(h => !Array.isArray(h) || h.length))
|
||||
result.head.link = result.head.link?.filter(l => typeof l.href === 'string' && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */ && (!l.href.startsWith('_nuxt/components/islands/') || l.href.includes('AsyncServerComponent')))
|
||||
}
|
||||
result.props = {}
|
||||
result.components = {}
|
||||
@ -2241,7 +2238,10 @@ describe('component islands', () => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"components": {},
|
||||
"head": [],
|
||||
"head": {
|
||||
"link": [],
|
||||
"style": [],
|
||||
},
|
||||
"html": "<div data-island-uid> This is a .server (20ms) async component that was very long ... <div id="async-server-component-count">2</div><div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><!--[--><div style="display: contents;" data-island-uid data-island-slot="default"><!--teleport start--><!--teleport end--></div><!--]--></div>",
|
||||
"props": {},
|
||||
"slots": {},
|
||||
@ -2253,10 +2253,11 @@ describe('component islands', () => {
|
||||
it('render server component with selective client hydration', async () => {
|
||||
const result = await $fetch<NuxtIslandResponse>('/__nuxt_island/ServerWithClient')
|
||||
if (isDev()) {
|
||||
result.head = resolveHead(result.head).map(h => ({
|
||||
...h,
|
||||
link: h.link?.filter(l => typeof l.href !== 'string' || (!l.href.includes('_nuxt/components/islands/LongAsyncComponent') && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */)),
|
||||
})).filter(h => Object.values(h).some(h => !Array.isArray(h) || h.length))
|
||||
result.head.link = result.head.link?.filter(l => typeof l.href !== 'string' || (!l.href.includes('_nuxt/components/islands/LongAsyncComponent') && !l.href.includes('PureComponent') /* TODO: fix dev bug triggered by previous fetch of /islands */))
|
||||
|
||||
if (!result.head.link) {
|
||||
delete result.head.link
|
||||
}
|
||||
}
|
||||
const { components } = result
|
||||
result.components = {}
|
||||
@ -2268,7 +2269,10 @@ describe('component islands', () => {
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"components": {},
|
||||
"head": [],
|
||||
"head": {
|
||||
"link": [],
|
||||
"style": [],
|
||||
},
|
||||
"html": "<div data-island-uid> ServerWithClient.server.vue : <p>count: 0</p> This component should not be preloaded <div><!--[--><div>a</div><div>b</div><div>c</div><!--]--></div> This is not interactive <div class="sugar-counter"> Sugar Counter 12 x 1 = 12 <button> Inc </button></div><div class="interactive-component-wrapper" style="border:solid 1px red;"> The component below is not a slot but declared as interactive <!--[--><div style="display: contents;" data-island-uid data-island-component="Counter"></div><!--teleport start--><!--teleport end--><!--]--></div></div>",
|
||||
"slots": {},
|
||||
}
|
||||
@ -2297,16 +2301,14 @@ describe('component islands', () => {
|
||||
|
||||
if (isDev()) {
|
||||
const fixtureDir = normalize(fileURLToPath(new URL('./fixtures/basic', import.meta.url)))
|
||||
for (const head of result.head) {
|
||||
for (const key in head) {
|
||||
if (key === 'link') {
|
||||
head[key] = head[key]?.map((h) => {
|
||||
if (h.href) {
|
||||
h.href = resolveUnrefHeadInput(h.href).replace(fixtureDir, '/<rootDir>').replaceAll('//', '/')
|
||||
}
|
||||
return h
|
||||
})
|
||||
}
|
||||
for (const key in result.head) {
|
||||
if (key === 'link') {
|
||||
result.head[key] = result.head[key]?.map((h) => {
|
||||
if (h.href) {
|
||||
h.href = resolveUnrefHeadInput(h.href).replace(fixtureDir, '/<rootDir>').replaceAll('//', '/')
|
||||
}
|
||||
return h
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2314,35 +2316,30 @@ describe('component islands', () => {
|
||||
// TODO: fix rendering of styles in webpack
|
||||
if (!isDev() && !isWebpack) {
|
||||
expect(normaliseIslandResult(result).head).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"style": [
|
||||
{
|
||||
"innerHTML": "pre[data-v-xxxxx]{color:blue}",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
{
|
||||
"link": [],
|
||||
"style": [
|
||||
{
|
||||
"innerHTML": "pre[data-v-xxxxx]{color:blue}",
|
||||
},
|
||||
],
|
||||
}
|
||||
`)
|
||||
} else if (isDev() && !isWebpack) {
|
||||
// TODO: resolve dev bug triggered by earlier fetch of /vueuse-head page
|
||||
// https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/runtime/nitro/renderer.ts#L139
|
||||
result.head = resolveHead(result.head).map(h => ({
|
||||
...h,
|
||||
link: h.link?.filter(l => typeof l.href !== 'string' || !l.href.includes('SharedComponent')),
|
||||
})).filter(h => Object.values(h).some(h => !Array.isArray(h) || h.length))
|
||||
result.head.link = result.head.link?.filter(l => typeof l.href !== 'string' || !l.href.includes('SharedComponent'))
|
||||
|
||||
expect(result.head).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"link": [
|
||||
{
|
||||
"href": "/_nuxt/components/islands/PureComponent.vue?vue&type=style&index=0&scoped=c0c0cf89&lang.css",
|
||||
"rel": "stylesheet",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
{
|
||||
"link": [
|
||||
{
|
||||
"href": "/_nuxt/components/islands/PureComponent.vue?vue&type=style&index=0&scoped=c0c0cf89&lang.css",
|
||||
"rel": "stylesheet",
|
||||
},
|
||||
],
|
||||
"style": [],
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
@ -2687,24 +2684,19 @@ describe('Node.js compatibility for client-side', () => {
|
||||
})
|
||||
|
||||
function normaliseIslandResult (result: NuxtIslandResponse) {
|
||||
return {
|
||||
...result,
|
||||
head: result.head.map((h) => {
|
||||
if (h.style) {
|
||||
for (const style of h.style) {
|
||||
if (typeof style !== 'string') {
|
||||
if (style.innerHTML) {
|
||||
style.innerHTML = (style.innerHTML as string).replace(/data-v-[a-z0-9]+/g, 'data-v-xxxxx')
|
||||
}
|
||||
if (style.key) {
|
||||
style.key = style.key.replace(/-[a-z0-9]+$/i, '')
|
||||
}
|
||||
}
|
||||
if (result.head.style) {
|
||||
for (const style of result.head.style) {
|
||||
if (typeof style !== 'string') {
|
||||
if (style.innerHTML) {
|
||||
style.innerHTML = (style.innerHTML as string).replace(/data-v-[a-z0-9]+/g, 'data-v-xxxxx')
|
||||
}
|
||||
if (style.key) {
|
||||
style.key = style.key.replace(/-[a-z0-9]+$/i, '')
|
||||
}
|
||||
return h
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
describe('import components', () => {
|
||||
|
@ -5,8 +5,6 @@ import { parse } from 'devalue'
|
||||
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
|
||||
import { createError } from 'h3'
|
||||
import { getBrowser, url, useTestContext } from '@nuxt/test-utils/e2e'
|
||||
import type { Head } from '@unhead/vue'
|
||||
import { resolveUnrefHeadInput } from '@unhead/vue'
|
||||
|
||||
export const isRenderingJson = process.env.TEST_PAYLOAD !== 'js'
|
||||
|
||||
@ -128,7 +126,3 @@ export function parseData (html: string) {
|
||||
attrs: _attrs,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveHead (head: Head[]) {
|
||||
return head.map(i => resolveUnrefHeadInput(i) as Head)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user