mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 07:05:11 +00:00
feat(nuxt): add dedicated #teleports
element for ssr teleports (#25043)
This commit is contained in:
parent
27f9c55fc2
commit
536998727a
@ -29,7 +29,7 @@ import unheadPlugins from '#internal/unhead-plugins.mjs'
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
import type { NuxtPayload, NuxtSSRContext } from '#app'
|
||||
// @ts-expect-error virtual file
|
||||
import { appHead, appRootId, appRootTag } from '#internal/nuxt.config.mjs'
|
||||
import { appHead, appRootId, appRootTag, appTeleportId, appTeleportTag } from '#internal/nuxt.config.mjs'
|
||||
// @ts-expect-error virtual file
|
||||
import { buildAssetsURL, publicAssetsURL } from '#paths'
|
||||
|
||||
@ -137,7 +137,7 @@ const getSSRRenderer = lazyCachedFunction(async () => {
|
||||
if (import.meta.dev && process.env.NUXT_VITE_NODE_OPTIONS) {
|
||||
renderer.rendererContext.updateManifest(await getClientManifest())
|
||||
}
|
||||
return `<${appRootTag}${appRootId ? ` id="${appRootId}"` : ''}>${html}</${appRootTag}>`
|
||||
return APP_ROOT_OPEN_TAG + html + APP_ROOT_CLOSE_TAG
|
||||
}
|
||||
|
||||
return renderer
|
||||
@ -149,10 +149,11 @@ const getSPARenderer = lazyCachedFunction(async () => {
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
const spaTemplate = await import('#spa-template').then(r => r.template).catch(() => '')
|
||||
.then(r => APP_ROOT_OPEN_TAG + r + APP_ROOT_CLOSE_TAG)
|
||||
|
||||
const options = {
|
||||
manifest,
|
||||
renderToString: () => `<${appRootTag}${appRootId ? ` id="${appRootId}"` : ''}>${spaTemplate}</${appRootTag}>`,
|
||||
renderToString: () => spaTemplate,
|
||||
buildAssetsURL
|
||||
}
|
||||
// Create SPA renderer and cache the result for all requests
|
||||
@ -230,8 +231,15 @@ async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
|
||||
return ctx
|
||||
}
|
||||
|
||||
const HAS_APP_TELEPORTS = !!(appTeleportTag && appTeleportId)
|
||||
const APP_TELEPORT_OPEN_TAG = HAS_APP_TELEPORTS ? `<${appTeleportTag} id="${appTeleportId}">` : ''
|
||||
const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : ''
|
||||
|
||||
const APP_ROOT_OPEN_TAG = `<${appRootTag}${appRootId ? ` id="${appRootId}"` : ''}>`
|
||||
const APP_ROOT_CLOSE_TAG = `</${appRootTag}>`
|
||||
|
||||
const PAYLOAD_URL_RE = process.env.NUXT_JSON_PAYLOADS ? /\/_payload.json(\?.*)?$/ : /\/_payload.js(\?.*)?$/
|
||||
const ROOT_NODE_REGEX = new RegExp(`^<${appRootTag}${appRootId ? ` id="${appRootId}"` : ''}>([\\s\\S]*)</${appRootTag}>$`)
|
||||
const ROOT_NODE_REGEX = new RegExp(`^${APP_ROOT_OPEN_TAG}([\\s\\S]*)${APP_ROOT_CLOSE_TAG}$`)
|
||||
|
||||
const PRERENDER_NO_SSR_ROUTES = new Set(['/index.html', '/200.html', '/404.html'])
|
||||
|
||||
@ -459,7 +467,10 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
head: normalizeChunks([headTags, ssrContext.styles]),
|
||||
bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
|
||||
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
|
||||
body: [process.env.NUXT_COMPONENT_ISLANDS ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html],
|
||||
body: [
|
||||
process.env.NUXT_COMPONENT_ISLANDS ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html,
|
||||
APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportId}`]]) : '') + APP_TELEPORT_CLOSE_TAG
|
||||
],
|
||||
bodyAppend: [bodyTags]
|
||||
}
|
||||
|
||||
@ -534,7 +545,7 @@ function normalizeChunks (chunks: (string | undefined)[]) {
|
||||
return chunks.filter(Boolean).map(i => i!.trim())
|
||||
}
|
||||
|
||||
function joinTags (tags: string[]) {
|
||||
function joinTags (tags: Array<string | undefined>) {
|
||||
return tags.join('')
|
||||
}
|
||||
|
||||
|
@ -184,6 +184,21 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
rootTag: {
|
||||
$resolve: val => val || 'div'
|
||||
},
|
||||
|
||||
/**
|
||||
* Customize Nuxt root element tag.
|
||||
*/
|
||||
teleportTag: {
|
||||
$resolve: val => val || 'div'
|
||||
},
|
||||
|
||||
/**
|
||||
* Customize Nuxt Teleport element id.
|
||||
* @type {string | false}
|
||||
*/
|
||||
teleportId: {
|
||||
$resolve: val => val === false ? false : (val || 'teleports')
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2470,6 +2470,23 @@ describe('keepalive', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('teleports', () => {
|
||||
it('should append teleports to body', async () => {
|
||||
const html = await $fetch('/teleport')
|
||||
|
||||
// Teleport is prepended to body, before the __nuxt div
|
||||
expect(html).toContain('<div>Teleport</div><!--teleport anchor--><div id="__nuxt">')
|
||||
// Teleport start and end tag are rendered as expected
|
||||
expect(html).toContain('<div><!--teleport start--><!--teleport end--><h1>Normal content</h1></div>')
|
||||
})
|
||||
it('should render teleports to app teleports element', async () => {
|
||||
const html = await $fetch('/nuxt-teleport')
|
||||
|
||||
// Teleport is appended to body, after the __nuxt div
|
||||
expect(html).toContain('<div><!--teleport start--><!--teleport end--><h1>Normal content</h1></div></div></div><span id="nuxt-teleport"><div>Nuxt Teleport</div><!--teleport anchor--></span><script')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Node.js compatibility for client-side', () => {
|
||||
it('should work', async () => {
|
||||
const { page } = await renderPage('/node-compat')
|
||||
|
@ -32,7 +32,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"204k"')
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"205k"')
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"1335k"')
|
||||
@ -72,7 +72,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output-inline/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"523k"')
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"524k"')
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"78.0k"')
|
||||
|
2
test/fixtures/basic/nuxt.config.ts
vendored
2
test/fixtures/basic/nuxt.config.ts
vendored
@ -14,6 +14,8 @@ export default defineNuxtConfig({
|
||||
app: {
|
||||
pageTransition: true,
|
||||
layoutTransition: true,
|
||||
teleportId: 'nuxt-teleport',
|
||||
teleportTag: 'span',
|
||||
head: {
|
||||
charset: 'utf-8',
|
||||
link: [undefined],
|
||||
|
8
test/fixtures/basic/pages/nuxt-teleport.vue
vendored
Normal file
8
test/fixtures/basic/pages/nuxt-teleport.vue
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<teleport to="#nuxt-teleport">
|
||||
<div>Nuxt Teleport</div>
|
||||
</teleport>
|
||||
<h1>Normal content</h1>
|
||||
</div>
|
||||
</template>
|
8
test/fixtures/basic/pages/teleport.vue
vendored
Normal file
8
test/fixtures/basic/pages/teleport.vue
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<teleport to="body">
|
||||
<div>Teleport</div>
|
||||
</teleport>
|
||||
<h1>Normal content</h1>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user