fix(nuxt): render user-inserted links in island responses (#25219)

This commit is contained in:
Daniel Roe 2024-01-16 12:36:26 +00:00 committed by GitHub
parent 84eaf7ba2e
commit 20e88bb171
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 25 deletions

View File

@ -229,9 +229,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} }
// Check for island component rendering // Check for island component rendering
const islandContext = (process.env.NUXT_COMPONENT_ISLANDS && event.path.startsWith('/__nuxt_island')) const isRenderingIsland = (process.env.NUXT_COMPONENT_ISLANDS as unknown as boolean && event.path.startsWith('/__nuxt_island'))
? await getIslandContext(event) const islandContext = isRenderingIsland ? await getIslandContext(event) : undefined
: undefined
if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) { if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) {
return islandCache!.getItem(event.path) as Promise<Partial<RenderResponse>> return islandCache!.getItem(event.path) as Promise<Partial<RenderResponse>>
@ -241,7 +240,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
let url = ssrError?.url as string || islandContext?.url || event.path let url = ssrError?.url as string || islandContext?.url || event.path
// Whether we are rendering payload route // Whether we are rendering payload route
const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !islandContext const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !isRenderingIsland
if (isRenderingPayload) { if (isRenderingPayload) {
url = url.substring(0, url.lastIndexOf('/')) || '/' url = url.substring(0, url.lastIndexOf('/')) || '/'
@ -260,7 +259,9 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
}) })
// needed for hash hydration plugin to work // needed for hash hydration plugin to work
const headEntryOptions: HeadEntryOptions = { mode: 'server' } const headEntryOptions: HeadEntryOptions = { mode: 'server' }
head.push(appHead, headEntryOptions) if (!isRenderingIsland) {
head.push(appHead, headEntryOptions)
}
// Initialize ssr context // Initialize ssr context
const ssrContext: NuxtSSRContext = { const ssrContext: NuxtSSRContext = {
@ -270,7 +271,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
noSSR: noSSR:
!!(process.env.NUXT_NO_SSR) || !!(process.env.NUXT_NO_SSR) ||
event.context.nuxt?.noSSR || event.context.nuxt?.noSSR ||
(routeOptions.ssr === false && !islandContext) || (routeOptions.ssr === false && !isRenderingIsland) ||
(import.meta.prerender ? PRERENDER_NO_SSR_ROUTES.has(url) : false), (import.meta.prerender ? PRERENDER_NO_SSR_ROUTES.has(url) : false),
head, head,
error: !!ssrError, error: !!ssrError,
@ -281,7 +282,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} }
// Whether we are prerendering route // Whether we are prerendering route
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR && !islandContext const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !ssrContext.noSSR && !isRenderingIsland
const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(useRuntimeConfig().app.baseURL, url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js') : undefined const payloadURL = _PAYLOAD_EXTRACTION ? joinURL(useRuntimeConfig().app.baseURL, url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js') : undefined
if (import.meta.prerender) { if (import.meta.prerender) {
ssrContext.payload.prerenderedAt = Date.now() ssrContext.payload.prerenderedAt = Date.now()
@ -330,7 +331,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
await payloadCache!.setItem(withoutTrailingSlash(url), renderPayloadResponse(ssrContext)) await payloadCache!.setItem(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
} }
if (process.env.NUXT_INLINE_STYLES && !islandContext) { if (process.env.NUXT_INLINE_STYLES && !isRenderingIsland) {
const source = ssrContext.modules ?? ssrContext._registeredComponents const source = ssrContext.modules ?? ssrContext._registeredComponents
if (source) { if (source) {
for (const id of await getEntryIds()) { for (const id of await getEntryIds()) {
@ -340,7 +341,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} }
// Render inline styles // Render inline styles
const inlinedStyles = (process.env.NUXT_INLINE_STYLES || Boolean(islandContext)) const inlinedStyles = (process.env.NUXT_INLINE_STYLES || isRenderingIsland)
? await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? []) ? await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? [])
: [] : []
@ -349,7 +350,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
// Setup head // Setup head
const { styles, scripts } = getRequestDependencies(ssrContext, renderer.rendererContext) const { styles, scripts } = getRequestDependencies(ssrContext, renderer.rendererContext)
// 1.Extracted payload preloading // 1.Extracted payload preloading
if (_PAYLOAD_EXTRACTION) { if (_PAYLOAD_EXTRACTION && !isRenderingIsland) {
head.push({ head.push({
link: [ link: [
process.env.NUXT_JSON_PAYLOADS process.env.NUXT_JSON_PAYLOADS
@ -361,14 +362,18 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
// 2. Styles // 2. Styles
head.push({ style: inlinedStyles }) head.push({ style: inlinedStyles })
head.push({ if (!isRenderingIsland || import.meta.dev) {
link: Object.values(styles) const link = []
.map(resource => for (const style in styles) {
({ rel: 'stylesheet', href: renderer.rendererContext.buildAssetsURL(resource.file) }) const resource = styles[style]
) if (!import.meta.dev || (resource.file.includes('scoped') && !resource.file.includes('pages/'))) {
}, headEntryOptions) link.push({ rel: 'stylesheet', href: renderer.rendererContext.buildAssetsURL(resource.file) })
}
}
head.push({ link }, headEntryOptions)
}
if (!NO_SCRIPTS) { if (!NO_SCRIPTS && !isRenderingIsland) {
// 3. Resource Hints // 3. Resource Hints
// TODO: add priorities based on Capo // TODO: add priorities based on Capo
head.push({ head.push({
@ -395,7 +400,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
} }
// 5. Scripts // 5. Scripts
if (!routeOptions.experimentalNoScripts) { if (!routeOptions.experimentalNoScripts && !isRenderingIsland) {
head.push({ head.push({
script: Object.values(scripts).map(resource => (<Script> { script: Object.values(scripts).map(resource => (<Script> {
type: resource.module ? 'module' : null, type: resource.module ? 'module' : null,
@ -411,7 +416,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
// Create render context // Create render context
const htmlContext: NuxtRenderHTMLContext = { const htmlContext: NuxtRenderHTMLContext = {
island: Boolean(islandContext), island: isRenderingIsland,
htmlAttrs: htmlAttrs ? [htmlAttrs] : [], htmlAttrs: htmlAttrs ? [htmlAttrs] : [],
head: normalizeChunks([headTags, ssrContext.styles]), head: normalizeChunks([headTags, ssrContext.styles]),
bodyAttrs: bodyAttrs ? [bodyAttrs] : [], bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
@ -424,16 +429,15 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
await nitroApp.hooks.callHook('render:html', htmlContext, { event }) await nitroApp.hooks.callHook('render:html', htmlContext, { event })
// Response for component islands // Response for component islands
if (process.env.NUXT_COMPONENT_ISLANDS && islandContext) { if (isRenderingIsland && islandContext) {
const islandHead: NuxtIslandResponse['head'] = { const islandHead: NuxtIslandResponse['head'] = {
link: [], link: [],
style: [] style: []
} }
for (const tag of await head.resolveTags()) { for (const tag of await head.resolveTags()) {
if (tag.tag === 'link' && tag.props.rel === 'stylesheet' && tag.props.href.includes('scoped') && !tag.props.href.includes('pages/')) { if (tag.tag === 'link') {
islandHead.link.push({ ...tag.props, key: 'island-link-' + hash(tag.props.href) }) islandHead.link.push({ key: 'island-link-' + hash(tag.props), ...tag.props })
} } else if (tag.tag === 'style' && tag.innerHTML) {
if (tag.tag === 'style' && tag.innerHTML) {
islandHead.style.push({ key: 'island-style-' + hash(tag.innerHTML), innerHTML: tag.innerHTML }) islandHead.style.push({ key: 'island-style-' + hash(tag.innerHTML), innerHTML: tag.innerHTML })
} }
} }

View File

@ -32,7 +32,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output/server') const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"200k"`) expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"201k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir) const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1335k"`) expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1335k"`)