mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-31 07:40:33 +00:00
perf(nuxt): preload app manifest (#30017)
This commit is contained in:
parent
67a669fa13
commit
37fa91e973
@ -1,7 +1,7 @@
|
|||||||
import type { MatcherExport, RouteMatcher } from 'radix3'
|
import type { MatcherExport, RouteMatcher } from 'radix3'
|
||||||
import { createMatcherFromExport, createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
|
import { createMatcherFromExport, createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { useRuntimeConfig } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs'
|
import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
@ -24,9 +24,14 @@ function fetchManifest () {
|
|||||||
if (!isAppManifestEnabled) {
|
if (!isAppManifestEnabled) {
|
||||||
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
|
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
|
||||||
}
|
}
|
||||||
manifest = $fetch<NuxtAppManifest>(buildAssetsURL(`builds/meta/${useRuntimeConfig().app.buildId}.json`), {
|
if (import.meta.server) {
|
||||||
responseType: 'json',
|
// @ts-expect-error virtual file
|
||||||
})
|
manifest = import('#app-manifest')
|
||||||
|
} else {
|
||||||
|
manifest = $fetch<NuxtAppManifest>(buildAssetsURL(`builds/meta/${useRuntimeConfig().app.buildId}.json`), {
|
||||||
|
responseType: 'json',
|
||||||
|
})
|
||||||
|
}
|
||||||
manifest.then((m) => {
|
manifest.then((m) => {
|
||||||
matcher = createMatcherFromExport(m.matcher)
|
matcher = createMatcherFromExport(m.matcher)
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
@ -40,12 +45,16 @@ export function getAppManifest (): Promise<NuxtAppManifest> {
|
|||||||
if (!isAppManifestEnabled) {
|
if (!isAppManifestEnabled) {
|
||||||
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
|
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
|
||||||
}
|
}
|
||||||
|
if (import.meta.server) {
|
||||||
|
useNuxtApp().ssrContext!._preloadManifest = true
|
||||||
|
}
|
||||||
return manifest || fetchManifest()
|
return manifest || fetchManifest()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 3.7.4 */
|
/** @since 3.7.4 */
|
||||||
export async function getRouteRules (url: string) {
|
export async function getRouteRules (url: string) {
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
|
useNuxtApp().ssrContext!._preloadManifest = true
|
||||||
const _routeRulesMatcher = toRouteMatcher(
|
const _routeRulesMatcher = toRouteMatcher(
|
||||||
createRadixRouter({ routes: useRuntimeConfig().nitro!.routeRules }),
|
createRadixRouter({ routes: useRuntimeConfig().nitro!.routeRules }),
|
||||||
)
|
)
|
||||||
|
@ -85,15 +85,18 @@ async function _importPayload (payloadURL: string) {
|
|||||||
}
|
}
|
||||||
/** @since 3.0.0 */
|
/** @since 3.0.0 */
|
||||||
export async function isPrerendered (url = useRoute().path) {
|
export async function isPrerendered (url = useRoute().path) {
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
// Note: Alternative for server is checking x-nitro-prerender header
|
// Note: Alternative for server is checking x-nitro-prerender header
|
||||||
if (!appManifest) { return !!useNuxtApp().payload.prerenderedAt }
|
if (!appManifest) { return !!nuxtApp.payload.prerenderedAt }
|
||||||
url = withoutTrailingSlash(url)
|
url = withoutTrailingSlash(url)
|
||||||
const manifest = await getAppManifest()
|
const manifest = await getAppManifest()
|
||||||
if (manifest.prerendered.includes(url)) {
|
if (manifest.prerendered.includes(url)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const rules = await getRouteRules(url)
|
return nuxtApp.runWithContext(async () => {
|
||||||
return !!rules.prerender && !rules.redirect
|
const rules = await getRouteRules(url)
|
||||||
|
return !!rules.prerender && !rules.redirect
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let payloadCache: NuxtPayload | null = null
|
let payloadCache: NuxtPayload | null = null
|
||||||
|
@ -83,6 +83,8 @@ export interface NuxtSSRContext extends SSRContext {
|
|||||||
get<T = unknown> (key: string): Promise<T> | undefined
|
get<T = unknown> (key: string): Promise<T> | undefined
|
||||||
set<T> (key: string, value: Promise<T>): Promise<void>
|
set<T> (key: string, value: Promise<T>): Promise<void>
|
||||||
}
|
}
|
||||||
|
/** @internal */
|
||||||
|
_preloadManifest?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NuxtPayload {
|
export interface NuxtPayload {
|
||||||
|
@ -264,7 +264,18 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
|
|
||||||
nuxt.options.alias['#app-manifest'] = join(tempDir, `meta/${buildId}.json`)
|
nuxt.options.alias['#app-manifest'] = join(tempDir, `meta/${buildId}.json`)
|
||||||
|
|
||||||
|
// write stub manifest before build so external import of #app-manifest can be resolved
|
||||||
|
if (!nuxt.options.dev) {
|
||||||
|
nuxt.hook('build:before', async () => {
|
||||||
|
await fsp.mkdir(join(tempDir, 'meta'), { recursive: true })
|
||||||
|
await fsp.writeFile(join(tempDir, `meta/${buildId}.json`), JSON.stringify({}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
nuxt.hook('nitro:config', (config) => {
|
nuxt.hook('nitro:config', (config) => {
|
||||||
|
config.alias ||= {}
|
||||||
|
config.alias['#app-manifest'] = join(tempDir, `meta/${buildId}.json`)
|
||||||
|
|
||||||
const rules = config.routeRules
|
const rules = config.routeRules
|
||||||
for (const rule in rules) {
|
for (const rule in rules) {
|
||||||
if (!(rules[rule] as any).appMiddleware) { continue }
|
if (!(rules[rule] as any).appMiddleware) { continue }
|
||||||
@ -340,6 +351,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add stub alias to allow vite to resolve import
|
||||||
|
if (!nuxt.options.experimental.appManifest) {
|
||||||
|
nuxt.options.alias['#app-manifest'] = 'unenv/runtime/mock/proxy'
|
||||||
|
}
|
||||||
|
|
||||||
// Add fallback server for `ssr: false`
|
// Add fallback server for `ssr: false`
|
||||||
const FORWARD_SLASH_RE = /\//g
|
const FORWARD_SLASH_RE = /\//g
|
||||||
if (!nuxt.options.ssr) {
|
if (!nuxt.options.ssr) {
|
||||||
|
@ -31,7 +31,7 @@ import { renderSSRHeadOptions } from '#internal/unhead.config.mjs'
|
|||||||
|
|
||||||
import type { NuxtPayload, NuxtSSRContext } from '#app'
|
import type { NuxtPayload, NuxtSSRContext } from '#app'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { appHead, appId, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands, multiApp } from '#internal/nuxt.config.mjs'
|
import { appHead, appId, appRootAttrs, appRootTag, appTeleportAttrs, appTeleportTag, componentIslands, appManifest as isAppManifestEnabled, multiApp } from '#internal/nuxt.config.mjs'
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
|
import { buildAssetsURL, publicAssetsURL } from '#internal/nuxt/paths'
|
||||||
|
|
||||||
@ -380,7 +380,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. Preload payloads and app manifest
|
||||||
if (_PAYLOAD_EXTRACTION && !NO_SCRIPTS && !isRenderingIsland) {
|
if (_PAYLOAD_EXTRACTION && !NO_SCRIPTS && !isRenderingIsland) {
|
||||||
head.push({
|
head.push({
|
||||||
link: [
|
link: [
|
||||||
@ -390,7 +390,13 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
],
|
],
|
||||||
}, headEntryOptions)
|
}, headEntryOptions)
|
||||||
}
|
}
|
||||||
|
if (isAppManifestEnabled && ssrContext._preloadManifest) {
|
||||||
|
head.push({
|
||||||
|
link: [
|
||||||
|
{ rel: 'preload', as: 'fetch', fetchpriority: 'low', crossorigin: 'anonymous', href: buildAssetsURL(`builds/meta/${ssrContext.runtimeConfig.app.buildId}.json`) },
|
||||||
|
],
|
||||||
|
}, { ...headEntryOptions, tagPriority: 'low' })
|
||||||
|
}
|
||||||
// 2. Styles
|
// 2. Styles
|
||||||
if (inlinedStyles.length) {
|
if (inlinedStyles.length) {
|
||||||
head.push({ style: inlinedStyles })
|
head.push({ style: inlinedStyles })
|
||||||
|
@ -85,6 +85,7 @@ export async function buildServer (ctx: ViteBuildContext) {
|
|||||||
external: [
|
external: [
|
||||||
'#internal/nitro',
|
'#internal/nitro',
|
||||||
'#internal/nuxt/paths',
|
'#internal/nuxt/paths',
|
||||||
|
'#app-manifest',
|
||||||
'#shared',
|
'#shared',
|
||||||
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared)))),
|
new RegExp('^' + escapeStringRegexp(withTrailingSlash(resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared)))),
|
||||||
],
|
],
|
||||||
|
@ -59,7 +59,7 @@ function serverStandalone (ctx: WebpackConfigContext) {
|
|||||||
resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared),
|
resolve(ctx.nuxt.options.rootDir, ctx.nuxt.options.dir.shared),
|
||||||
]
|
]
|
||||||
if (!ctx.nuxt.options.dev) {
|
if (!ctx.nuxt.options.dev) {
|
||||||
external.push('#internal/nuxt/paths')
|
external.push('#internal/nuxt/paths', '#app-manifest')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(ctx.config.externals)) { return }
|
if (!Array.isArray(ctx.config.externals)) { return }
|
||||||
|
Loading…
Reference in New Issue
Block a user