Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nuxt): add dedicated #teleports element for ssr teleports #25043

Merged
merged 22 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
72c845a
feat: add basic teleport test
manniL Jan 4, 2024
0b13943
feat: add the app teleport element
manniL Jan 4, 2024
28ff961
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 4, 2024
f3edb39
chore: style tweaks
danielroe Jan 29, 2024
e5a9522
Merge remote-tracking branch 'origin/main' into fix/teleports
danielroe Jan 29, 2024
89753cc
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 29, 2024
03de3d9
style: lint
danielroe Jan 29, 2024
b7b52e0
fix: remove teleports and root from components islands as expected
manniL Feb 18, 2024
1323503
Merge branch 'main' into fix/teleports
manniL Feb 18, 2024
582aa73
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 18, 2024
07adc88
Merge branch 'main' into fix/teleports
manniL Feb 24, 2024
f0d1758
Merge remote-tracking branch 'origin/main' into fix/teleports
danielroe Mar 8, 2024
7dff7b6
refactor: hoist out open/close teleport tags
danielroe Mar 8, 2024
171ee60
refactor: hoist tag strings out of template function
danielroe Mar 8, 2024
a7166f3
perf: wrap spa template once
danielroe Mar 8, 2024
81cd8eb
fix: guard creation of appTeleports
danielroe Mar 8, 2024
bff3571
Update packages/schema/src/config/app.ts
manniL Mar 10, 2024
52de4b5
Merge remote-tracking branch 'origin/main' into fix/teleports
danielroe Mar 11, 2024
de47501
chore: update comment
danielroe Mar 11, 2024
1e9df18
perf: reduce string manipulation
danielroe Mar 11, 2024
0b51f8f
chore: add back space
danielroe Mar 11, 2024
44bf24f
chore: add back space and reduce diff
danielroe Mar 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 17 additions & 6 deletions packages/nuxt/src/core/runtime/nitro/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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'])

Expand Down Expand Up @@ -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]
}

Expand Down Expand Up @@ -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('')
}

Expand Down
15 changes: 15 additions & 0 deletions packages/schema/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ export default defineUntypedSchema({
*/
rootTag: {
$resolve: val => val || 'div'
},

danielroe marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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')
}
},

Expand Down
17 changes: 17 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 2 additions & 2 deletions test/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"')
Expand Down Expand Up @@ -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"')
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/basic/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default defineNuxtConfig({
app: {
pageTransition: true,
layoutTransition: true,
teleportId: 'nuxt-teleport',
teleportTag: 'span',
head: {
charset: 'utf-8',
link: [undefined],
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/basic/pages/nuxt-teleport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div>
<teleport to="#nuxt-teleport">
<div>Nuxt Teleport</div>
</teleport>
<h1>Normal content</h1>
</div>
</template>
8 changes: 8 additions & 0 deletions test/fixtures/basic/pages/teleport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div>
<teleport to="body">
<div>Teleport</div>
</teleport>
<h1>Normal content</h1>
</div>
</template>