mirror of
https://github.com/nuxt/nuxt.git
synced 2025-03-17 14:21:54 +00:00
refactor(nuxt): re-organize internal runtime/nitro
files (#31131)
This commit is contained in:
parent
563d449010
commit
1baf3ebad6
@ -700,7 +700,7 @@ These options have been set to their current values for some time and we do not
|
||||
|
||||
* `polyfillVueUseHead` is implementable in user-land with [this plugin](https://github.com/nuxt/nuxt/blob/f209158352b09d1986aa320e29ff36353b91c358/packages/nuxt/src/head/runtime/plugins/vueuse-head-polyfill.ts#L10-L11)
|
||||
|
||||
* `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9)
|
||||
* `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/middleware/no-ssr.ts#L8-L9)
|
||||
|
||||
## Nuxt 2 vs Nuxt 3+
|
||||
|
||||
|
@ -95,9 +95,9 @@ See [Nitro](https://nitro.unjs.io/guide/plugins#available-hooks) for all availab
|
||||
Hook | Arguments | Description | Types
|
||||
-----------------------|-----------------------|--------------------------------------|------------------
|
||||
`dev:ssr-logs` | `{ path, logs }` | Server | Called at the end of a request cycle with an array of server-side logs.
|
||||
`render:response` | `response, { event }` | Called before sending the response. | [response](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/renderer.ts#L24), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`render:html` | `html, { event }` | Called before constructing the HTML. | [html](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/renderer.ts#L15), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`render:island` | `islandResponse, { event, islandContext }` | Called before constructing the island HTML. | [islandResponse](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L28), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), [islandContext](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L38)
|
||||
`render:response` | `response, { event }` | Called before sending the response. | [response](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts#L24), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`render:html` | `html, { event }` | Called before constructing the HTML. | [html](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts#L15), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`render:island` | `islandResponse, { event, islandContext }` | Called before constructing the island HTML. | [islandResponse](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts#L28), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), [islandContext](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts#L38)
|
||||
`close` | - | Called when Nitro is closed. | -
|
||||
`error` | `error, { event? }` | Called when an error occurs. | [error](https://github.com/nitrojs/nitro/blob/d20ffcbd16fc4003b774445e1a01e698c2bb078a/src/types/runtime/nitro.ts#L48), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
`request` | `event` | Called when a request is received. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
|
||||
|
@ -21,7 +21,7 @@ export default createConfigForNuxt({
|
||||
'packages/schema/schema/**',
|
||||
'packages/nuxt/src/app/components/welcome.vue',
|
||||
'packages/nuxt/src/app/components/error-*.vue',
|
||||
'packages/nuxt/src/core/runtime/nitro/error-*',
|
||||
'packages/nuxt/src/core/runtime/nitro/handlers/error-*',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
4
packages/nuxt/.gitignore
vendored
4
packages/nuxt/.gitignore
vendored
@ -2,5 +2,5 @@ src/app/components/error-404.vue
|
||||
src/app/components/error-500.vue
|
||||
src/app/components/error-dev.vue
|
||||
src/app/components/welcome.vue
|
||||
src/core/runtime/nitro/error-500.ts
|
||||
src/core/runtime/nitro/error-dev.ts
|
||||
src/core/runtime/nitro/handlers/error-500.ts
|
||||
src/core/runtime/nitro/handlers/error-dev.ts
|
||||
|
@ -4,4 +4,4 @@ export interface NuxtAppLiterals {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from '../core/runtime/nitro/renderer'
|
||||
export type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from '../core/runtime/nitro/handlers/renderer'
|
||||
|
@ -84,18 +84,18 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
{
|
||||
as: '__buildAssetsURL',
|
||||
name: 'buildAssetsURL',
|
||||
from: resolve(distDir, 'core/runtime/nitro/paths'),
|
||||
from: resolve(distDir, 'core/runtime/nitro/utils/paths'),
|
||||
},
|
||||
{
|
||||
as: '__publicAssetsURL',
|
||||
name: 'publicAssetsURL',
|
||||
from: resolve(distDir, 'core/runtime/nitro/paths'),
|
||||
from: resolve(distDir, 'core/runtime/nitro/utils/paths'),
|
||||
},
|
||||
{
|
||||
// TODO: Remove after https://github.com/nitrojs/nitro/issues/1049
|
||||
as: 'defineAppConfig',
|
||||
name: 'defineAppConfig',
|
||||
from: resolve(distDir, 'core/runtime/nitro/config'),
|
||||
from: resolve(distDir, 'core/runtime/nitro/utils/config'),
|
||||
priority: -1,
|
||||
},
|
||||
],
|
||||
@ -112,7 +112,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
: false,
|
||||
scanDirs: nuxt.options._layers.map(layer => (layer.config.serverDir || layer.config.srcDir) && resolve(layer.cwd, layer.config.serverDir || resolve(layer.config.srcDir, 'server'))).filter(Boolean),
|
||||
renderer: resolve(distDir, 'core/runtime/nitro/renderer'),
|
||||
renderer: resolve(distDir, 'core/runtime/nitro/handlers/renderer'),
|
||||
nodeModulesDirs: nuxt.options.modulesDir,
|
||||
handlers: nuxt.options.serverHandlers,
|
||||
devHandlers: [],
|
||||
@ -210,7 +210,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
...nuxt.options.alias,
|
||||
|
||||
// Paths
|
||||
'#internal/nuxt/paths': resolve(distDir, 'core/runtime/nitro/paths'),
|
||||
'#internal/nuxt/paths': resolve(distDir, 'core/runtime/nitro/utils/paths'),
|
||||
},
|
||||
replace: {
|
||||
'process.env.NUXT_NO_SSR': nuxt.options.ssr === false,
|
||||
@ -234,13 +234,13 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
nitroConfig.imports.imports ||= []
|
||||
nitroConfig.imports.imports.push({
|
||||
name: 'useAppConfig',
|
||||
from: resolve(distDir, 'core/runtime/nitro/app-config'),
|
||||
from: resolve(distDir, 'core/runtime/nitro/utils/app-config'),
|
||||
})
|
||||
}
|
||||
|
||||
// add error handler
|
||||
if (!nitroConfig.errorHandler && (nuxt.options.dev || !nuxt.options.experimental.noVueServer)) {
|
||||
nitroConfig.errorHandler = resolve(distDir, 'core/runtime/nitro/error')
|
||||
nitroConfig.errorHandler = resolve(distDir, 'core/runtime/nitro/handlers/error')
|
||||
}
|
||||
|
||||
// Resolve user-provided paths
|
||||
@ -421,7 +421,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
ImpoundPlugin.rollup({
|
||||
cwd: nuxt.options.rootDir,
|
||||
patterns: createImportProtectionPatterns(nuxt, { context: 'nitro-app' }),
|
||||
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/, ...sharedPatterns],
|
||||
exclude: [/core[\\/]runtime[\\/]nitro[\\/](?:handlers|utils)/, ...sharedPatterns],
|
||||
}),
|
||||
)
|
||||
|
||||
@ -443,7 +443,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
})
|
||||
|
||||
const cacheDir = resolve(nuxt.options.buildDir, 'cache/nitro/prerender')
|
||||
const cacheDriverPath = join(distDir, 'core/runtime/nitro/cache-driver.js')
|
||||
const cacheDriverPath = join(distDir, 'core/runtime/nitro/utils/cache-driver.js')
|
||||
await fsp.rm(cacheDir, { recursive: true, force: true }).catch(() => {})
|
||||
nitro.options._config.storage = defu(nitro.options._config.storage, {
|
||||
'internal:nuxt:prerender': {
|
||||
@ -506,13 +506,13 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
nitro.options.handlers.unshift({
|
||||
route: '/__nuxt_error',
|
||||
lazy: true,
|
||||
handler: resolve(distDir, 'core/runtime/nitro/renderer'),
|
||||
handler: resolve(distDir, 'core/runtime/nitro/handlers/renderer'),
|
||||
})
|
||||
|
||||
if (!nuxt.options.dev && nuxt.options.experimental.noVueServer) {
|
||||
nitro.hooks.hook('rollup:before', (nitro) => {
|
||||
if (nitro.options.preset === 'nitro-prerender') {
|
||||
nitro.options.errorHandler = resolve(distDir, 'core/runtime/nitro/error')
|
||||
nitro.options.errorHandler = resolve(distDir, 'core/runtime/nitro/handlers/error')
|
||||
return
|
||||
}
|
||||
const nuxtErrorHandler = nitro.options.handlers.findIndex(h => h.route === '/__nuxt_error')
|
||||
|
@ -431,7 +431,7 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
|
||||
if (nuxt.options.dev && nuxt.options.features.devLogs) {
|
||||
addPlugin(resolve(nuxt.options.appDir, 'plugins/dev-server-logs'))
|
||||
addServerPlugin(resolve(distDir, 'core/runtime/nitro/dev-server-logs'))
|
||||
addServerPlugin(resolve(distDir, 'core/runtime/nitro/plugins/dev-server-logs'))
|
||||
nuxt.options.nitro = defu(nuxt.options.nitro, {
|
||||
externals: {
|
||||
inline: [/#internal\/dev-server-logs-options/],
|
||||
|
@ -1,36 +1,34 @@
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
import {
|
||||
createRenderer,
|
||||
getPrefetchLinks,
|
||||
getPreloadLinks,
|
||||
getRequestDependencies,
|
||||
renderResourceHeaders,
|
||||
} from 'vue-bundle-renderer/runtime'
|
||||
import type { Manifest as ClientManifest } from 'vue-bundle-renderer'
|
||||
import type { RenderResponse } from 'nitro/types'
|
||||
import type { Manifest } from 'vite'
|
||||
import type { H3Event } from 'h3'
|
||||
import { appendResponseHeader, createError, getQuery, getResponseStatus, getResponseStatusText, readBody, writeEarlyHints } from 'h3'
|
||||
import devalue from '@nuxt/devalue'
|
||||
import { stringify, uneval } from 'devalue'
|
||||
import destr from 'destr'
|
||||
import { getQuery as getURLQuery, joinURL, withoutTrailingSlash } from 'ufo'
|
||||
import { renderToString as _renderToString } from 'vue/server-renderer'
|
||||
import { propsToString, renderSSRHead } from '@unhead/ssr'
|
||||
import type { Head, HeadEntryOptions } from '@unhead/schema'
|
||||
import type { Link, Script, Style } from '@unhead/vue'
|
||||
import { createServerHead, resolveUnrefHeadInput } from '@unhead/vue'
|
||||
|
||||
import { defineRenderHandler, getRouteRules, useNitroApp, useRuntimeConfig, useStorage } from 'nitro/runtime'
|
||||
import { defineRenderHandler, getRouteRules, useNitroApp, useRuntimeConfig } from 'nitro/runtime'
|
||||
import type { NuxtPayload, NuxtSSRContext } from 'nuxt/app'
|
||||
|
||||
import { getEntryIds, getSPARenderer, getSSRRenderer, getSSRStyles } from '../utils/build-files'
|
||||
import { islandCache, islandPropCache, payloadCache, sharedPrerenderCache } from '../utils/cache'
|
||||
|
||||
import { renderPayloadJsonScript, renderPayloadResponse, renderPayloadScript, splitPayload } from '../utils/payload'
|
||||
// @ts-expect-error virtual file
|
||||
import unheadPlugins from '#internal/unhead-plugins.mjs'
|
||||
// @ts-expect-error virtual file
|
||||
import { renderSSRHeadOptions } from '#internal/unhead.config.mjs'
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
import { appHead, appId, appRootAttrs, appRootTag, appSpaLoaderAttrs, appSpaLoaderTag, appTeleportAttrs, appTeleportTag, componentIslands, appManifest as isAppManifestEnabled, multiApp, spaLoadingTemplateOutside } from '#internal/nuxt.config.mjs'
|
||||
import { appHead, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands, appManifest as isAppManifestEnabled } from '#internal/nuxt.config.mjs'
|
||||
// @ts-expect-error virtual file
|
||||
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
|
||||
|
||||
@ -91,119 +89,6 @@ export interface NuxtRenderResponse {
|
||||
headers: Record<string, string>
|
||||
}
|
||||
|
||||
// @ts-expect-error file will be produced after app build
|
||||
const getClientManifest: () => Promise<Manifest> = () => import('#build/dist/server/client.manifest.mjs')
|
||||
.then(r => r.default || r)
|
||||
.then(r => typeof r === 'function' ? r() : r) as Promise<ClientManifest>
|
||||
|
||||
const getEntryIds: () => Promise<string[]> = () => getClientManifest().then(r => Object.values(r).filter(r =>
|
||||
// @ts-expect-error internal key set by CSS inlining configuration
|
||||
r._globalCSS,
|
||||
).map(r => r.src!))
|
||||
|
||||
// @ts-expect-error file will be produced after app build
|
||||
const getServerEntry = () => import('#build/dist/server/server.mjs').then(r => r.default || r)
|
||||
|
||||
// @ts-expect-error file will be produced after app build
|
||||
const getSSRStyles = lazyCachedFunction((): Promise<Record<string, () => Promise<string[]>>> => import('#build/dist/server/styles.mjs').then(r => r.default || r))
|
||||
|
||||
// -- SSR Renderer --
|
||||
const getSSRRenderer = lazyCachedFunction(async () => {
|
||||
// Load client manifest
|
||||
const manifest = await getClientManifest()
|
||||
if (!manifest) { throw new Error('client.manifest is not available') }
|
||||
|
||||
// Load server bundle
|
||||
const createSSRApp = await getServerEntry()
|
||||
if (!createSSRApp) { throw new Error('Server bundle is not available') }
|
||||
|
||||
const options = {
|
||||
manifest,
|
||||
renderToString,
|
||||
buildAssetsURL,
|
||||
}
|
||||
// Create renderer
|
||||
const renderer = createRenderer(createSSRApp, options)
|
||||
|
||||
type RenderToStringParams = Parameters<typeof _renderToString>
|
||||
async function renderToString (input: RenderToStringParams[0], context: RenderToStringParams[1]) {
|
||||
const html = await _renderToString(input, context)
|
||||
// In development with vite-node, the manifest is on-demand and will be available after rendering
|
||||
if (import.meta.dev && process.env.NUXT_VITE_NODE_OPTIONS) {
|
||||
renderer.rendererContext.updateManifest(await getClientManifest())
|
||||
}
|
||||
return APP_ROOT_OPEN_TAG + html + APP_ROOT_CLOSE_TAG
|
||||
}
|
||||
|
||||
return renderer
|
||||
})
|
||||
|
||||
// -- SPA Renderer --
|
||||
const getSPARenderer = lazyCachedFunction(async () => {
|
||||
const manifest = await getClientManifest()
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
const spaTemplate = await import('#spa-template').then(r => r.template).catch(() => '')
|
||||
.then((r) => {
|
||||
if (spaLoadingTemplateOutside) {
|
||||
const APP_SPA_LOADER_OPEN_TAG = `<${appSpaLoaderTag}${propsToString(appSpaLoaderAttrs)}>`
|
||||
const APP_SPA_LOADER_CLOSE_TAG = `</${appSpaLoaderTag}>`
|
||||
const appTemplate = APP_ROOT_OPEN_TAG + APP_ROOT_CLOSE_TAG
|
||||
const loaderTemplate = r ? APP_SPA_LOADER_OPEN_TAG + r + APP_SPA_LOADER_CLOSE_TAG : ''
|
||||
return appTemplate + loaderTemplate
|
||||
} else {
|
||||
return APP_ROOT_OPEN_TAG + r + APP_ROOT_CLOSE_TAG
|
||||
}
|
||||
})
|
||||
|
||||
const options = {
|
||||
manifest,
|
||||
renderToString: () => spaTemplate,
|
||||
buildAssetsURL,
|
||||
}
|
||||
// Create SPA renderer and cache the result for all requests
|
||||
const renderer = createRenderer(() => () => {}, options)
|
||||
const result = await renderer.renderToString({})
|
||||
|
||||
const renderToString = (ssrContext: NuxtSSRContext) => {
|
||||
const config = useRuntimeConfig(ssrContext.event)
|
||||
ssrContext.modules ||= new Set<string>()
|
||||
ssrContext.payload.serverRendered = false
|
||||
ssrContext.config = {
|
||||
public: config.public,
|
||||
app: config.app,
|
||||
}
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
return {
|
||||
rendererContext: renderer.rendererContext,
|
||||
renderToString,
|
||||
}
|
||||
})
|
||||
|
||||
const payloadCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:payload') : null
|
||||
const islandCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:island') : null
|
||||
const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:island-props') : null
|
||||
const sharedPrerenderPromises = import.meta.prerender && process.env.NUXT_SHARED_DATA ? new Map<string, Promise<any>>() : null
|
||||
const sharedPrerenderKeys = new Set<string>()
|
||||
const sharedPrerenderCache = import.meta.prerender && process.env.NUXT_SHARED_DATA
|
||||
? {
|
||||
get<T = unknown> (key: string): Promise<T> | undefined {
|
||||
if (sharedPrerenderKeys.has(key)) {
|
||||
return sharedPrerenderPromises!.get(key) ?? useStorage('internal:nuxt:prerender:shared').getItem(key) as Promise<T>
|
||||
}
|
||||
},
|
||||
async set<T> (key: string, value: Promise<T>): Promise<void> {
|
||||
sharedPrerenderKeys.add(key)
|
||||
sharedPrerenderPromises!.set(key, value)
|
||||
useStorage('internal:nuxt:prerender:shared').setItem(key, await value as any)
|
||||
// free up memory after the promise is resolved
|
||||
.finally(() => sharedPrerenderPromises!.delete(key))
|
||||
},
|
||||
}
|
||||
: null
|
||||
|
||||
const ISLAND_SUFFIX_RE = /\.json(\?.*)?$/
|
||||
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
|
||||
// TODO: Strict validation for url
|
||||
@ -236,9 +121,6 @@ const HAS_APP_TELEPORTS = !!(appTeleportTag && appTeleportAttrs.id)
|
||||
const APP_TELEPORT_OPEN_TAG = HAS_APP_TELEPORTS ? `<${appTeleportTag}${propsToString(appTeleportAttrs)}>` : ''
|
||||
const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : ''
|
||||
|
||||
const APP_ROOT_OPEN_TAG = `<${appRootTag}${propsToString(appRootAttrs)}>`
|
||||
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}[^>]*>([\\s\\S]*)<\\/${appRootTag}>$`)
|
||||
|
||||
@ -548,16 +430,6 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
return response
|
||||
})
|
||||
|
||||
function lazyCachedFunction<T> (fn: () => Promise<T>): () => Promise<T> {
|
||||
let res: Promise<T> | null = null
|
||||
return () => {
|
||||
if (res === null) {
|
||||
res = fn().catch((err) => { res = null; throw err })
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeChunks (chunks: (string | undefined)[]) {
|
||||
return chunks.filter(Boolean).map(i => i!.trim())
|
||||
}
|
||||
@ -592,76 +464,6 @@ async function renderInlineStyles (usedModules: Set<string> | string[]): Promise
|
||||
return Array.from(inlinedStyles).map(style => ({ innerHTML: style }))
|
||||
}
|
||||
|
||||
function renderPayloadResponse (ssrContext: NuxtSSRContext) {
|
||||
return {
|
||||
body: process.env.NUXT_JSON_PAYLOADS
|
||||
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
|
||||
: `export default ${devalue(splitPayload(ssrContext).payload)}`,
|
||||
statusCode: getResponseStatus(ssrContext.event),
|
||||
statusMessage: getResponseStatusText(ssrContext.event),
|
||||
headers: {
|
||||
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt',
|
||||
},
|
||||
} satisfies RenderResponse
|
||||
}
|
||||
|
||||
function renderPayloadJsonScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] {
|
||||
const contents = opts.data ? stringify(opts.data, opts.ssrContext._payloadReducers) : ''
|
||||
const payload: Script = {
|
||||
'type': 'application/json',
|
||||
'innerHTML': contents,
|
||||
'data-nuxt-data': appId,
|
||||
'data-ssr': !(process.env.NUXT_NO_SSR || opts.ssrContext.noSSR),
|
||||
}
|
||||
if (!multiApp) {
|
||||
payload.id = '__NUXT_DATA__'
|
||||
}
|
||||
if (opts.src) {
|
||||
payload['data-src'] = opts.src
|
||||
}
|
||||
const config = uneval(opts.ssrContext.config)
|
||||
return [
|
||||
payload,
|
||||
{
|
||||
innerHTML: multiApp
|
||||
? `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={config:${config}}`
|
||||
: `window.__NUXT__={};window.__NUXT__.config=${config}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function renderPayloadScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] {
|
||||
opts.data.config = opts.ssrContext.config
|
||||
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !opts.ssrContext.noSSR
|
||||
const nuxtData = devalue(opts.data)
|
||||
if (_PAYLOAD_EXTRACTION) {
|
||||
const singleAppPayload = `import p from "${opts.src}";window.__NUXT__={...p,...(${nuxtData})}`
|
||||
const multiAppPayload = `import p from "${opts.src}";window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={...p,...(${nuxtData})}`
|
||||
return [
|
||||
{
|
||||
type: 'module',
|
||||
innerHTML: multiApp ? multiAppPayload : singleAppPayload,
|
||||
},
|
||||
]
|
||||
}
|
||||
const singleAppPayload = `window.__NUXT__=${nuxtData}`
|
||||
const multiAppPayload = `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]=${nuxtData}`
|
||||
return [
|
||||
{
|
||||
innerHTML: multiApp ? multiAppPayload : singleAppPayload,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function splitPayload (ssrContext: NuxtSSRContext) {
|
||||
const { data, prerenderedAt, ...initial } = ssrContext.payload
|
||||
return {
|
||||
initial: { ...initial, prerenderedAt },
|
||||
payload: { data, prerenderedAt },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the root node from the html body
|
||||
*/
|
119
packages/nuxt/src/core/runtime/nitro/utils/build-files.ts
Normal file
119
packages/nuxt/src/core/runtime/nitro/utils/build-files.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import {
|
||||
createRenderer,
|
||||
} from 'vue-bundle-renderer/runtime'
|
||||
import type { Manifest as ClientManifest } from 'vue-bundle-renderer'
|
||||
import type { Manifest } from 'vite'
|
||||
import { renderToString as _renderToString } from 'vue/server-renderer'
|
||||
import { propsToString } from '@unhead/ssr'
|
||||
|
||||
import { useRuntimeConfig } from 'nitro/runtime'
|
||||
import type { NuxtSSRContext } from 'nuxt/app'
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
import { appRootAttrs, appRootTag, appSpaLoaderAttrs, appSpaLoaderTag, spaLoadingTemplateOutside } from '#internal/nuxt.config.mjs'
|
||||
// @ts-expect-error virtual file
|
||||
import { buildAssetsURL } from '#internal/nuxt/paths'
|
||||
|
||||
const APP_ROOT_OPEN_TAG = `<${appRootTag}${propsToString(appRootAttrs)}>`
|
||||
const APP_ROOT_CLOSE_TAG = `</${appRootTag}>`
|
||||
|
||||
// @ts-expect-error file will be produced after app build
|
||||
export const getClientManifest: () => Promise<Manifest> = () => import('#build/dist/server/client.manifest.mjs')
|
||||
.then(r => r.default || r)
|
||||
.then(r => typeof r === 'function' ? r() : r) as Promise<ClientManifest>
|
||||
|
||||
export const getEntryIds: () => Promise<string[]> = () => getClientManifest().then(r => Object.values(r).filter(r =>
|
||||
// @ts-expect-error internal key set by CSS inlining configuration
|
||||
r._globalCSS,
|
||||
).map(r => r.src!))
|
||||
|
||||
// @ts-expect-error file will be produced after app build
|
||||
export const getServerEntry = () => import('#build/dist/server/server.mjs').then(r => r.default || r)
|
||||
|
||||
// @ts-expect-error file will be produced after app build
|
||||
export const getSSRStyles = lazyCachedFunction((): Promise<Record<string, () => Promise<string[]>>> => import('#build/dist/server/styles.mjs').then(r => r.default || r))
|
||||
|
||||
// -- SSR Renderer --
|
||||
export const getSSRRenderer = lazyCachedFunction(async () => {
|
||||
// Load client manifest
|
||||
const manifest = await getClientManifest()
|
||||
if (!manifest) { throw new Error('client.manifest is not available') }
|
||||
|
||||
// Load server bundle
|
||||
const createSSRApp = await getServerEntry()
|
||||
if (!createSSRApp) { throw new Error('Server bundle is not available') }
|
||||
|
||||
const options = {
|
||||
manifest,
|
||||
renderToString,
|
||||
buildAssetsURL,
|
||||
}
|
||||
// Create renderer
|
||||
const renderer = createRenderer(createSSRApp, options)
|
||||
|
||||
type RenderToStringParams = Parameters<typeof _renderToString>
|
||||
async function renderToString (input: RenderToStringParams[0], context: RenderToStringParams[1]) {
|
||||
const html = await _renderToString(input, context)
|
||||
// In development with vite-node, the manifest is on-demand and will be available after rendering
|
||||
if (import.meta.dev && process.env.NUXT_VITE_NODE_OPTIONS) {
|
||||
renderer.rendererContext.updateManifest(await getClientManifest())
|
||||
}
|
||||
return APP_ROOT_OPEN_TAG + html + APP_ROOT_CLOSE_TAG
|
||||
}
|
||||
|
||||
return renderer
|
||||
})
|
||||
|
||||
// -- SPA Renderer --
|
||||
export const getSPARenderer = lazyCachedFunction(async () => {
|
||||
const manifest = await getClientManifest()
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
const spaTemplate = await import('#spa-template').then(r => r.template).catch(() => '')
|
||||
.then((r) => {
|
||||
if (spaLoadingTemplateOutside) {
|
||||
const APP_SPA_LOADER_OPEN_TAG = `<${appSpaLoaderTag}${propsToString(appSpaLoaderAttrs)}>`
|
||||
const APP_SPA_LOADER_CLOSE_TAG = `</${appSpaLoaderTag}>`
|
||||
const appTemplate = APP_ROOT_OPEN_TAG + APP_ROOT_CLOSE_TAG
|
||||
const loaderTemplate = r ? APP_SPA_LOADER_OPEN_TAG + r + APP_SPA_LOADER_CLOSE_TAG : ''
|
||||
return appTemplate + loaderTemplate
|
||||
} else {
|
||||
return APP_ROOT_OPEN_TAG + r + APP_ROOT_CLOSE_TAG
|
||||
}
|
||||
})
|
||||
|
||||
const options = {
|
||||
manifest,
|
||||
renderToString: () => spaTemplate,
|
||||
buildAssetsURL,
|
||||
}
|
||||
// Create SPA renderer and cache the result for all requests
|
||||
const renderer = createRenderer(() => () => {}, options)
|
||||
const result = await renderer.renderToString({})
|
||||
|
||||
const renderToString = (ssrContext: NuxtSSRContext) => {
|
||||
const config = useRuntimeConfig(ssrContext.event)
|
||||
ssrContext.modules ||= new Set<string>()
|
||||
ssrContext.payload.serverRendered = false
|
||||
ssrContext.config = {
|
||||
public: config.public,
|
||||
app: config.app,
|
||||
}
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
return {
|
||||
rendererContext: renderer.rendererContext,
|
||||
renderToString,
|
||||
}
|
||||
})
|
||||
|
||||
function lazyCachedFunction<T> (fn: () => Promise<T>): () => Promise<T> {
|
||||
let res: Promise<T> | null = null
|
||||
return () => {
|
||||
if (res === null) {
|
||||
res = fn().catch((err) => { res = null; throw err })
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
24
packages/nuxt/src/core/runtime/nitro/utils/cache.ts
Normal file
24
packages/nuxt/src/core/runtime/nitro/utils/cache.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { useStorage } from 'nitro/runtime'
|
||||
|
||||
export const payloadCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:payload') : null
|
||||
export const islandCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:island') : null
|
||||
export const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:prerender:island-props') : null
|
||||
export const sharedPrerenderPromises = import.meta.prerender && process.env.NUXT_SHARED_DATA ? new Map<string, Promise<any>>() : null
|
||||
|
||||
const sharedPrerenderKeys = new Set<string>()
|
||||
export const sharedPrerenderCache = import.meta.prerender && process.env.NUXT_SHARED_DATA
|
||||
? {
|
||||
get<T = unknown> (key: string): Promise<T> | undefined {
|
||||
if (sharedPrerenderKeys.has(key)) {
|
||||
return sharedPrerenderPromises!.get(key) ?? useStorage('internal:nuxt:prerender:shared').getItem(key) as Promise<T>
|
||||
}
|
||||
},
|
||||
async set<T> (key: string, value: Promise<T>): Promise<void> {
|
||||
sharedPrerenderKeys.add(key)
|
||||
sharedPrerenderPromises!.set(key, value)
|
||||
useStorage('internal:nuxt:prerender:shared').setItem(key, await value as any)
|
||||
// free up memory after the promise is resolved
|
||||
.finally(() => sharedPrerenderPromises!.delete(key))
|
||||
},
|
||||
}
|
||||
: null
|
80
packages/nuxt/src/core/runtime/nitro/utils/payload.ts
Normal file
80
packages/nuxt/src/core/runtime/nitro/utils/payload.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { RenderResponse } from 'nitro/types'
|
||||
import { getResponseStatus, getResponseStatusText } from 'h3'
|
||||
import devalue from '@nuxt/devalue'
|
||||
import { stringify, uneval } from 'devalue'
|
||||
import type { Script } from '@unhead/vue'
|
||||
|
||||
import type { NuxtSSRContext } from 'nuxt/app'
|
||||
|
||||
// @ts-expect-error virtual file
|
||||
import { appId, multiApp } from '#internal/nuxt.config.mjs'
|
||||
|
||||
export function renderPayloadResponse (ssrContext: NuxtSSRContext) {
|
||||
return {
|
||||
body: process.env.NUXT_JSON_PAYLOADS
|
||||
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
|
||||
: `export default ${devalue(splitPayload(ssrContext).payload)}`,
|
||||
statusCode: getResponseStatus(ssrContext.event),
|
||||
statusMessage: getResponseStatusText(ssrContext.event),
|
||||
headers: {
|
||||
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt',
|
||||
},
|
||||
} satisfies RenderResponse
|
||||
}
|
||||
|
||||
export function renderPayloadJsonScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] {
|
||||
const contents = opts.data ? stringify(opts.data, opts.ssrContext._payloadReducers) : ''
|
||||
const payload: Script = {
|
||||
'type': 'application/json',
|
||||
'innerHTML': contents,
|
||||
'data-nuxt-data': appId,
|
||||
'data-ssr': !(process.env.NUXT_NO_SSR || opts.ssrContext.noSSR),
|
||||
}
|
||||
if (!multiApp) {
|
||||
payload.id = '__NUXT_DATA__'
|
||||
}
|
||||
if (opts.src) {
|
||||
payload['data-src'] = opts.src
|
||||
}
|
||||
const config = uneval(opts.ssrContext.config)
|
||||
return [
|
||||
payload,
|
||||
{
|
||||
innerHTML: multiApp
|
||||
? `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={config:${config}}`
|
||||
: `window.__NUXT__={};window.__NUXT__.config=${config}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function renderPayloadScript (opts: { ssrContext: NuxtSSRContext, data?: any, src?: string }): Script[] {
|
||||
opts.data.config = opts.ssrContext.config
|
||||
const _PAYLOAD_EXTRACTION = import.meta.prerender && process.env.NUXT_PAYLOAD_EXTRACTION && !opts.ssrContext.noSSR
|
||||
const nuxtData = devalue(opts.data)
|
||||
if (_PAYLOAD_EXTRACTION) {
|
||||
const singleAppPayload = `import p from "${opts.src}";window.__NUXT__={...p,...(${nuxtData})}`
|
||||
const multiAppPayload = `import p from "${opts.src}";window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={...p,...(${nuxtData})}`
|
||||
return [
|
||||
{
|
||||
type: 'module',
|
||||
innerHTML: multiApp ? multiAppPayload : singleAppPayload,
|
||||
},
|
||||
]
|
||||
}
|
||||
const singleAppPayload = `window.__NUXT__=${nuxtData}`
|
||||
const multiAppPayload = `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]=${nuxtData}`
|
||||
return [
|
||||
{
|
||||
innerHTML: multiApp ? multiAppPayload : singleAppPayload,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function splitPayload (ssrContext: NuxtSSRContext) {
|
||||
const { data, prerenderedAt, ...initial } = ssrContext.payload
|
||||
return {
|
||||
initial: { ...initial, prerenderedAt },
|
||||
payload: { data, prerenderedAt },
|
||||
}
|
||||
}
|
@ -483,7 +483,7 @@ export const publicPathTemplate: NuxtTemplate = {
|
||||
' return path.length ? joinRelativeURL(publicBase, ...path) : publicBase',
|
||||
'}',
|
||||
|
||||
// On server these are registered directly in packages/nuxt/src/core/runtime/nitro/renderer.ts
|
||||
// On server these are registered directly in packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts
|
||||
'if (import.meta.client) {',
|
||||
' globalThis.__buildAssetsURL = buildAssetsURL',
|
||||
' globalThis.__publicAssetsURL = publicAssetsURL',
|
||||
|
@ -201,7 +201,7 @@ export const RenderPlugin = () => {
|
||||
await copyFile(r(`dist/templates/${file}`), join(nuxtRoot, 'src/app/components', file))
|
||||
}
|
||||
for (const file of ['error-500.ts', 'error-dev.ts']) {
|
||||
await copyFile(r(`dist/templates/${file}`), join(nuxtRoot, 'src/core/runtime/nitro', file))
|
||||
await copyFile(r(`dist/templates/${file}`), join(nuxtRoot, 'src/core/runtime/nitro/handlers', file))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -2426,7 +2426,7 @@ describe('component islands', () => {
|
||||
`)
|
||||
} 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
|
||||
// https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts#L139
|
||||
result.head.link = result.head.link?.filter(l => typeof l.href !== 'string' || !l.href.includes('SharedComponent'))
|
||||
|
||||
expect(result.head).toMatchInlineSnapshot(`
|
||||
|
Loading…
Reference in New Issue
Block a user