Split the client reference manifest file to be generated per-entry (#52450)
This PR changes client manifest generation process. Instead of one big manifest file that contains client references for the entire app, we're now generating one manifest file per entry which only covers client components that can be reached in the module graph.
This commit is contained in:
parent
c68c4bdc9f
commit
990c58c5ef
20 changed files with 274 additions and 125 deletions
|
@ -52,7 +52,6 @@ import {
|
|||
PAGES_MANIFEST,
|
||||
PHASE_PRODUCTION_BUILD,
|
||||
PRERENDER_MANIFEST,
|
||||
CLIENT_REFERENCE_MANIFEST,
|
||||
REACT_LOADABLE_MANIFEST,
|
||||
ROUTES_MANIFEST,
|
||||
SERVER_DIRECTORY,
|
||||
|
@ -903,14 +902,6 @@ export default async function build(
|
|||
: []),
|
||||
path.join(SERVER_DIRECTORY, APP_PATHS_MANIFEST),
|
||||
APP_BUILD_MANIFEST,
|
||||
path.join(
|
||||
SERVER_DIRECTORY,
|
||||
CLIENT_REFERENCE_MANIFEST + '.js'
|
||||
),
|
||||
path.join(
|
||||
SERVER_DIRECTORY,
|
||||
CLIENT_REFERENCE_MANIFEST + '.json'
|
||||
),
|
||||
path.join(
|
||||
SERVER_DIRECTORY,
|
||||
SERVER_REFERENCE_MANIFEST + '.js'
|
||||
|
@ -1487,7 +1478,6 @@ export default async function build(
|
|||
pageRuntime,
|
||||
edgeInfo,
|
||||
pageType,
|
||||
hasServerComponents: !!appDir,
|
||||
incrementalCacheHandlerPath:
|
||||
config.experimental.incrementalCacheHandlerPath,
|
||||
isrFlushToDisk: config.experimental.isrFlushToDisk,
|
||||
|
|
|
@ -1339,7 +1339,6 @@ export async function isPageStatic({
|
|||
pageRuntime,
|
||||
edgeInfo,
|
||||
pageType,
|
||||
hasServerComponents,
|
||||
originalAppPath,
|
||||
isrFlushToDisk,
|
||||
maxMemoryCacheSize,
|
||||
|
@ -1356,7 +1355,6 @@ export async function isPageStatic({
|
|||
edgeInfo?: any
|
||||
pageType?: 'pages' | 'app'
|
||||
pageRuntime?: ServerRuntime
|
||||
hasServerComponents?: boolean
|
||||
originalAppPath?: string
|
||||
isrFlushToDisk?: boolean
|
||||
maxMemoryCacheSize?: number
|
||||
|
@ -1425,7 +1423,6 @@ export async function isPageStatic({
|
|||
componentsResult = await loadComponents({
|
||||
distDir,
|
||||
pathname: originalAppPath || page,
|
||||
hasServerComponents: !!hasServerComponents,
|
||||
isAppPath: pageType === 'app',
|
||||
})
|
||||
}
|
||||
|
@ -1669,7 +1666,6 @@ export async function hasCustomGetInitialProps(
|
|||
const components = await loadComponents({
|
||||
distDir,
|
||||
pathname: page,
|
||||
hasServerComponents: false,
|
||||
isAppPath: false,
|
||||
})
|
||||
let mod = components.ComponentMod
|
||||
|
@ -1692,7 +1688,6 @@ export async function getDefinedNamedExports(
|
|||
const components = await loadComponents({
|
||||
distDir,
|
||||
pathname: page,
|
||||
hasServerComponents: false,
|
||||
isAppPath: false,
|
||||
})
|
||||
|
||||
|
|
|
@ -216,7 +216,9 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
|
|||
const buildManifest = self.__BUILD_MANIFEST
|
||||
const prerenderManifest = maybeJSONParse(self.__PRERENDER_MANIFEST)
|
||||
const reactLoadableManifest = maybeJSONParse(self.__REACT_LOADABLE_MANIFEST)
|
||||
const rscManifest = maybeJSONParse(self.__RSC_MANIFEST)
|
||||
const rscManifest = maybeJSONParse(self.__RSC_MANIFEST?.[${JSON.stringify(
|
||||
page
|
||||
)}])
|
||||
const rscServerManifest = maybeJSONParse(self.__RSC_SERVER_MANIFEST)
|
||||
const subresourceIntegrityManifest = ${
|
||||
sriEnabled
|
||||
|
|
|
@ -70,6 +70,7 @@ export function getRender({
|
|||
nextFontManifest,
|
||||
Document,
|
||||
App: appMod?.default as AppType,
|
||||
clientReferenceManifest,
|
||||
}
|
||||
|
||||
const server = new WebServer({
|
||||
|
@ -86,7 +87,6 @@ export function getRender({
|
|||
runtime: SERVER_RUNTIME.experimentalEdge,
|
||||
supportsDynamicHTML: true,
|
||||
disableOptimizedLoading: true,
|
||||
clientReferenceManifest,
|
||||
serverActionsManifest,
|
||||
serverActionsBodySizeLimit,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
|
||||
import {
|
||||
APP_BUILD_MANIFEST,
|
||||
CLIENT_REFERENCE_MANIFEST,
|
||||
CLIENT_STATIC_FILES_RUNTIME_MAIN_APP,
|
||||
SYSTEM_ENTRYPOINTS,
|
||||
} from '../../../shared/lib/constants'
|
||||
|
@ -76,8 +77,22 @@ export class AppBuildManifestPlugin {
|
|||
}
|
||||
|
||||
const filesForPage = getEntrypointFiles(entrypoint)
|
||||
const manifestsForPage =
|
||||
pagePath.endsWith('/page') ||
|
||||
pagePath === '/not-found' ||
|
||||
pagePath === '/_not-found'
|
||||
? [
|
||||
'server/app' +
|
||||
pagePath.replace(/%5F/g, '_') +
|
||||
'_' +
|
||||
CLIENT_REFERENCE_MANIFEST +
|
||||
'.js',
|
||||
]
|
||||
: []
|
||||
|
||||
manifest.pages[pagePath] = [...new Set([...mainFiles, ...filesForPage])]
|
||||
manifest.pages[pagePath] = [
|
||||
...new Set([...mainFiles, ...manifestsForPage, ...filesForPage]),
|
||||
]
|
||||
}
|
||||
|
||||
const json = JSON.stringify(manifest, null, 2)
|
||||
|
|
|
@ -71,6 +71,43 @@ export type ClientReferenceManifest = {
|
|||
}
|
||||
}
|
||||
|
||||
function getAppPathRequiredChunks(chunkGroup: webpack.ChunkGroup) {
|
||||
return chunkGroup.chunks
|
||||
.map((requiredChunk: webpack.Chunk) => {
|
||||
if (SYSTEM_ENTRYPOINTS.has(requiredChunk.name || '')) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get the actual chunk file names from the chunk file list.
|
||||
// It's possible that the chunk is generated via `import()`, in
|
||||
// that case the chunk file name will be '[name].[contenthash]'
|
||||
// instead of '[name]-[chunkhash]'.
|
||||
return [...requiredChunk.files].map((file) => {
|
||||
// It's possible that a chunk also emits CSS files, that will
|
||||
// be handled separatedly.
|
||||
if (!file.endsWith('.js')) return null
|
||||
if (file.endsWith('.hot-update.js')) return null
|
||||
|
||||
return requiredChunk.id + ':' + file
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
.filter(nonNullable)
|
||||
}
|
||||
|
||||
function mergeManifest(
|
||||
manifest: ClientReferenceManifest,
|
||||
manifestToMerge: ClientReferenceManifest
|
||||
) {
|
||||
Object.assign(manifest.clientModules, manifestToMerge.clientModules)
|
||||
Object.assign(manifest.ssrModuleMapping, manifestToMerge.ssrModuleMapping)
|
||||
Object.assign(
|
||||
manifest.edgeSSRModuleMapping,
|
||||
manifestToMerge.edgeSSRModuleMapping
|
||||
)
|
||||
Object.assign(manifest.entryCSSFiles, manifestToMerge.entryCSSFiles)
|
||||
}
|
||||
|
||||
const PLUGIN_NAME = 'ClientReferenceManifestPlugin'
|
||||
|
||||
export class ClientReferenceManifestPlugin {
|
||||
|
@ -116,43 +153,21 @@ export class ClientReferenceManifestPlugin {
|
|||
compilation: webpack.Compilation,
|
||||
context: string
|
||||
) {
|
||||
const manifest: ClientReferenceManifest = {
|
||||
ssrModuleMapping: {},
|
||||
edgeSSRModuleMapping: {},
|
||||
clientModules: {},
|
||||
entryCSSFiles: {},
|
||||
}
|
||||
const manifestsPerGroup = new Map<string, ClientReferenceManifest[]>()
|
||||
|
||||
compilation.chunkGroups.forEach((chunkGroup) => {
|
||||
function getAppPathRequiredChunks() {
|
||||
return chunkGroup.chunks
|
||||
.map((requiredChunk: webpack.Chunk) => {
|
||||
if (SYSTEM_ENTRYPOINTS.has(requiredChunk.name || '')) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get the actual chunk file names from the chunk file list.
|
||||
// It's possible that the chunk is generated via `import()`, in
|
||||
// that case the chunk file name will be '[name].[contenthash]'
|
||||
// instead of '[name]-[chunkhash]'.
|
||||
return [...requiredChunk.files].map((file) => {
|
||||
// It's possible that a chunk also emits CSS files, that will
|
||||
// be handled separatedly.
|
||||
if (!file.endsWith('.js')) return null
|
||||
if (file.endsWith('.hot-update.js')) return null
|
||||
|
||||
return requiredChunk.id + ':' + file
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
.filter(nonNullable)
|
||||
// By default it's the shared chunkGroup (main-app) for every page.
|
||||
let entryName = ''
|
||||
const manifest: ClientReferenceManifest = {
|
||||
ssrModuleMapping: {},
|
||||
edgeSSRModuleMapping: {},
|
||||
clientModules: {},
|
||||
entryCSSFiles: {},
|
||||
}
|
||||
const requiredChunks = getAppPathRequiredChunks()
|
||||
|
||||
let chunkEntryName: string | null = null
|
||||
if (chunkGroup.name && /^app[\\/]/.test(chunkGroup.name)) {
|
||||
// Absolute path without the extension
|
||||
chunkEntryName = (this.appDirBase + chunkGroup.name).replace(
|
||||
const chunkEntryName = (this.appDirBase + chunkGroup.name).replace(
|
||||
/[\\/]/g,
|
||||
path.sep
|
||||
)
|
||||
|
@ -161,8 +176,11 @@ export class ClientReferenceManifestPlugin {
|
|||
.filter(
|
||||
(f) => !f.startsWith('static/css/pages/') && f.endsWith('.css')
|
||||
)
|
||||
|
||||
entryName = chunkGroup.name
|
||||
}
|
||||
|
||||
const requiredChunks = getAppPathRequiredChunks(chunkGroup)
|
||||
const recordModule = (id: ModuleId, mod: webpack.NormalModule) => {
|
||||
// Skip all modules from the pages folder.
|
||||
if (mod.layer !== WEBPACK_LAYERS.appClient) {
|
||||
|
@ -311,22 +329,95 @@ export class ClientReferenceManifestPlugin {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
// A page's entry name can have extensions. For example, these are both valid:
|
||||
// - app/foo/page
|
||||
// - app/foo/page.page
|
||||
// Let's normalize the entry name to remove the extra extension
|
||||
const groupName = /\/page(\.[^/]+)?$/.test(entryName)
|
||||
? entryName.replace(/\/page(\.[^/]+)?$/, '/page')
|
||||
: entryName.slice(0, entryName.lastIndexOf('/'))
|
||||
|
||||
if (!manifestsPerGroup.has(groupName)) {
|
||||
manifestsPerGroup.set(groupName, [])
|
||||
}
|
||||
manifestsPerGroup.get(groupName)!.push(manifest)
|
||||
|
||||
if (entryName.includes('/@')) {
|
||||
// Remove parallel route labels:
|
||||
// - app/foo/@bar/page -> app/foo
|
||||
// - app/foo/@bar/layout -> app/foo/layout -> app/foo
|
||||
const entryNameWithoutNamedSegments = entryName.replace(/\/@[^/]+/g, '')
|
||||
const groupNameWithoutNamedSegments =
|
||||
entryNameWithoutNamedSegments.slice(
|
||||
0,
|
||||
entryNameWithoutNamedSegments.lastIndexOf('/')
|
||||
)
|
||||
if (!manifestsPerGroup.has(groupNameWithoutNamedSegments)) {
|
||||
manifestsPerGroup.set(groupNameWithoutNamedSegments, [])
|
||||
}
|
||||
manifestsPerGroup.get(groupNameWithoutNamedSegments)!.push(manifest)
|
||||
}
|
||||
|
||||
// Special case for the root not-found page.
|
||||
if (/^app\/not-found(\.[^.]+)?$/.test(entryName)) {
|
||||
if (!manifestsPerGroup.has('app/not-found')) {
|
||||
manifestsPerGroup.set('app/not-found', [])
|
||||
}
|
||||
manifestsPerGroup.get('app/not-found')!.push(manifest)
|
||||
}
|
||||
})
|
||||
|
||||
const file = 'server/' + CLIENT_REFERENCE_MANIFEST
|
||||
const json = JSON.stringify(manifest, null, this.dev ? 2 : undefined)
|
||||
// Generate per-page manifests.
|
||||
for (const [groupName] of manifestsPerGroup) {
|
||||
if (groupName.endsWith('/page') || groupName === 'app/not-found') {
|
||||
const mergedManifest: ClientReferenceManifest = {
|
||||
ssrModuleMapping: {},
|
||||
edgeSSRModuleMapping: {},
|
||||
clientModules: {},
|
||||
entryCSSFiles: {},
|
||||
}
|
||||
|
||||
const segments = groupName.split('/')
|
||||
let group = ''
|
||||
for (const segment of segments) {
|
||||
if (segment.startsWith('@')) continue
|
||||
for (const manifest of manifestsPerGroup.get(group) || []) {
|
||||
mergeManifest(mergedManifest, manifest)
|
||||
}
|
||||
group += (group ? '/' : '') + segment
|
||||
}
|
||||
for (const manifest of manifestsPerGroup.get(groupName) || []) {
|
||||
mergeManifest(mergedManifest, manifest)
|
||||
}
|
||||
|
||||
const json = JSON.stringify(mergedManifest)
|
||||
|
||||
const pagePath = groupName.replace(/%5F/g, '_')
|
||||
assets['server/' + pagePath + '_' + CLIENT_REFERENCE_MANIFEST + '.js'] =
|
||||
new sources.RawSource(
|
||||
`globalThis.__RSC_MANIFEST=(globalThis.__RSC_MANIFEST||{});globalThis.__RSC_MANIFEST[${JSON.stringify(
|
||||
pagePath.slice('app'.length)
|
||||
)}]=${JSON.stringify(json)}`
|
||||
) as unknown as webpack.sources.RawSource
|
||||
|
||||
if (pagePath === 'app/not-found') {
|
||||
// Create a separate special manifest for the root not-found page.
|
||||
assets[
|
||||
'server/' +
|
||||
'app/_not-found' +
|
||||
'_' +
|
||||
CLIENT_REFERENCE_MANIFEST +
|
||||
'.js'
|
||||
] = new sources.RawSource(
|
||||
`globalThis.__RSC_MANIFEST=(globalThis.__RSC_MANIFEST||{});globalThis.__RSC_MANIFEST[${JSON.stringify(
|
||||
'/_not-found'
|
||||
)}]=${JSON.stringify(json)}`
|
||||
) as unknown as webpack.sources.RawSource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pluginState.ASYNC_CLIENT_MODULES = []
|
||||
|
||||
assets[file + '.js'] = new sources.RawSource(
|
||||
`self.__RSC_MANIFEST=${JSON.stringify(json)}`
|
||||
// Work around webpack 4 type of RawSource being used
|
||||
// TODO: use webpack 5 type by default
|
||||
) as unknown as webpack.sources.RawSource
|
||||
assets[file + '.json'] = new sources.RawSource(
|
||||
json
|
||||
// Work around webpack 4 type of RawSource being used
|
||||
// TODO: use webpack 5 type by default
|
||||
) as unknown as webpack.sources.RawSource
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
CLIENT_REFERENCE_MANIFEST,
|
||||
MIDDLEWARE_MANIFEST,
|
||||
MIDDLEWARE_REACT_LOADABLE_MANIFEST,
|
||||
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
|
||||
SUBRESOURCE_INTEGRITY_MANIFEST,
|
||||
NEXT_FONT_MANIFEST,
|
||||
SERVER_REFERENCE_MANIFEST,
|
||||
|
@ -99,7 +98,6 @@ function getEntryFiles(
|
|||
if (meta.edgeSSR) {
|
||||
if (meta.edgeSSR.isServerComponent) {
|
||||
files.push(`server/${SERVER_REFERENCE_MANIFEST}.js`)
|
||||
files.push(`server/${CLIENT_REFERENCE_MANIFEST}.js`)
|
||||
if (opts.sriEnabled) {
|
||||
files.push(`server/${SUBRESOURCE_INTEGRITY_MANIFEST}.js`)
|
||||
}
|
||||
|
@ -107,13 +105,12 @@ function getEntryFiles(
|
|||
...entryFiles
|
||||
.filter(
|
||||
(file) =>
|
||||
file.startsWith('pages/') && !file.endsWith('.hot-update.js')
|
||||
file.startsWith('app/') && !file.endsWith('.hot-update.js')
|
||||
)
|
||||
.map(
|
||||
(file) =>
|
||||
'server/' +
|
||||
// TODO-APP: seems this should be removed.
|
||||
file.replace('.js', NEXT_CLIENT_SSR_ENTRY_SUFFIX + '.js')
|
||||
file.replace('.js', '_' + CLIENT_REFERENCE_MANIFEST + '.js')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -135,6 +132,7 @@ function getEntryFiles(
|
|||
.filter((file) => !file.endsWith('.hot-update.js'))
|
||||
.map((file) => 'server/' + file)
|
||||
)
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
CLIENT_STATIC_FILES_PATH,
|
||||
EXPORT_DETAIL,
|
||||
EXPORT_MARKER,
|
||||
CLIENT_REFERENCE_MANIFEST,
|
||||
NEXT_FONT_MANIFEST,
|
||||
MIDDLEWARE_MANIFEST,
|
||||
PAGES_MANIFEST,
|
||||
|
@ -482,11 +481,6 @@ export default async function exportApp(
|
|||
images: nextConfig.images,
|
||||
...(options.hasAppDir
|
||||
? {
|
||||
clientReferenceManifest: require(join(
|
||||
distDir,
|
||||
SERVER_DIRECTORY,
|
||||
CLIENT_REFERENCE_MANIFEST + '.json'
|
||||
)),
|
||||
serverActionsManifest: require(join(
|
||||
distDir,
|
||||
SERVER_DIRECTORY,
|
||||
|
@ -727,7 +721,6 @@ export default async function exportApp(
|
|||
nextConfig.experimental.disableOptimizedLoading,
|
||||
parentSpanId: pageExportSpan.id,
|
||||
httpAgentOptions: nextConfig.httpAgentOptions,
|
||||
serverComponents: options.hasAppDir,
|
||||
debugOutput: options.debugOutput,
|
||||
isrMemoryCacheSize: nextConfig.experimental.isrMemoryCacheSize,
|
||||
fetchCache: nextConfig.experimental.appDir,
|
||||
|
|
|
@ -79,7 +79,6 @@ interface ExportPageInput {
|
|||
disableOptimizedLoading: any
|
||||
parentSpanId: any
|
||||
httpAgentOptions: NextConfigComplete['httpAgentOptions']
|
||||
serverComponents?: boolean
|
||||
debugOutput?: boolean
|
||||
isrMemoryCacheSize?: NextConfigComplete['experimental']['isrMemoryCacheSize']
|
||||
fetchCache?: boolean
|
||||
|
@ -137,7 +136,6 @@ export default async function exportPage({
|
|||
optimizeCss,
|
||||
disableOptimizedLoading,
|
||||
httpAgentOptions,
|
||||
serverComponents,
|
||||
debugOutput,
|
||||
isrMemoryCacheSize,
|
||||
fetchCache,
|
||||
|
@ -313,7 +311,6 @@ export default async function exportPage({
|
|||
components = await loadComponents({
|
||||
distDir,
|
||||
pathname: page,
|
||||
hasServerComponents: !!serverComponents,
|
||||
isAppPath: isAppDir,
|
||||
})
|
||||
curRenderOpts = {
|
||||
|
|
|
@ -1247,6 +1247,10 @@ export async function renderToHTMLOrFlight(
|
|||
rscChunks: [],
|
||||
}
|
||||
|
||||
if (!clientReferenceManifest) {
|
||||
console.log(req.url)
|
||||
}
|
||||
|
||||
const validateRootLayout = dev
|
||||
? {
|
||||
validateRootLayout: {
|
||||
|
|
|
@ -270,7 +270,6 @@ export default abstract class Server<ServerOptions extends Options = Options> {
|
|||
protected router: Router
|
||||
protected appPathRoutes?: Record<string, string[]>
|
||||
protected customRoutes: CustomRoutes
|
||||
protected clientReferenceManifest?: ClientReferenceManifest
|
||||
protected nextFontManifest?: NextFontManifest
|
||||
public readonly hostname?: string
|
||||
public readonly port?: number
|
||||
|
@ -294,7 +293,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
|
|||
}): Promise<FindComponentsResult | null>
|
||||
protected abstract getFontManifest(): FontManifest | undefined
|
||||
protected abstract getPrerenderManifest(): PrerenderManifest
|
||||
protected abstract getServerComponentManifest(): any
|
||||
// protected abstract getServerComponentManifest(): any
|
||||
protected abstract getNextFontManifest(): NextFontManifest | undefined
|
||||
protected abstract attachRequestMeta(
|
||||
req: BaseNextRequest,
|
||||
|
@ -419,9 +418,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
|
|||
this.hasAppDir =
|
||||
!!this.nextConfig.experimental.appDir && this.getHasAppDir(dev)
|
||||
const serverComponents = this.hasAppDir
|
||||
this.clientReferenceManifest = serverComponents
|
||||
? this.getServerComponentManifest()
|
||||
: undefined
|
||||
|
||||
this.nextFontManifest = this.getNextFontManifest()
|
||||
|
||||
if (process.env.NEXT_RUNTIME !== 'edge') {
|
||||
|
@ -1389,9 +1386,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
|
|||
}
|
||||
|
||||
// Don't delete headers[RSC] yet, it still needs to be used in renderToHTML later
|
||||
const isFlightRequest = Boolean(
|
||||
this.clientReferenceManifest && req.headers[RSC.toLowerCase()]
|
||||
)
|
||||
const isFlightRequest = Boolean(req.headers[RSC.toLowerCase()])
|
||||
|
||||
// For pages we need to ensure the correct Vary header is set too, to avoid
|
||||
// caching issues when navigating between pages and app
|
||||
|
@ -1781,9 +1776,10 @@ export default abstract class Server<ServerOptions extends Options = Options> {
|
|||
components.routeModule
|
||||
) {
|
||||
const module = components.routeModule as PagesRouteModule
|
||||
renderOpts.clientReferenceManifest = components.clientReferenceManifest
|
||||
|
||||
// Due to the way we pass data by mutating `renderOpts`, we can't extend
|
||||
// the object here but only updating its `clientReferenceManifest`
|
||||
// the object here but only updating its `nextFontManifest`
|
||||
// field.
|
||||
// https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952
|
||||
renderOpts.nextFontManifest = this.nextFontManifest
|
||||
|
|
|
@ -1442,10 +1442,6 @@ export default class DevServer extends Server {
|
|||
return this.middleware
|
||||
}
|
||||
|
||||
protected getServerComponentManifest() {
|
||||
return undefined
|
||||
}
|
||||
|
||||
protected getNextFontManifest() {
|
||||
return undefined
|
||||
}
|
||||
|
@ -1755,11 +1751,6 @@ export default class DevServer extends Server {
|
|||
})
|
||||
}
|
||||
|
||||
// When the new page is compiled, we need to reload the server component
|
||||
// manifest.
|
||||
if (!!this.appDir) {
|
||||
this.clientReferenceManifest = super.getServerComponentManifest()
|
||||
}
|
||||
this.nextFontManifest = super.getNextFontManifest()
|
||||
// before we re-evaluate a route module, we want to restore globals that might
|
||||
// have been patched previously to their original state so that we don't
|
||||
|
|
|
@ -63,7 +63,6 @@ export async function loadStaticPaths({
|
|||
const components = await loadComponents({
|
||||
distDir,
|
||||
pathname: originalAppPath || pathname,
|
||||
hasServerComponents: false,
|
||||
isAppPath: !!isAppPath,
|
||||
})
|
||||
|
||||
|
|
|
@ -92,15 +92,26 @@ async function loadManifest<T>(manifestPath: string, attempts = 3): Promise<T> {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadJSManifest<T>(
|
||||
manifestPath: string,
|
||||
name: string,
|
||||
entryname: string
|
||||
): Promise<T | undefined> {
|
||||
await loadManifest(manifestPath)
|
||||
try {
|
||||
return JSON.parse((globalThis as any)[name][entryname]) as T
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function loadComponentsImpl({
|
||||
distDir,
|
||||
pathname,
|
||||
hasServerComponents,
|
||||
isAppPath,
|
||||
}: {
|
||||
distDir: string
|
||||
pathname: string
|
||||
hasServerComponents: boolean
|
||||
isAppPath: boolean
|
||||
}): Promise<LoadComponentsReturnType> {
|
||||
let DocumentMod = {}
|
||||
|
@ -115,6 +126,13 @@ async function loadComponentsImpl({
|
|||
requirePage(pathname, distDir, isAppPath)
|
||||
)
|
||||
|
||||
// Make sure to avoid loading the manifest for Route Handlers
|
||||
const hasClientManifest =
|
||||
isAppPath &&
|
||||
(pathname.endsWith('/page') ||
|
||||
pathname === '/not-found' ||
|
||||
pathname === '/_not-found')
|
||||
|
||||
const [
|
||||
buildManifest,
|
||||
reactLoadableManifest,
|
||||
|
@ -123,12 +141,22 @@ async function loadComponentsImpl({
|
|||
] = await Promise.all([
|
||||
loadManifest<BuildManifest>(join(distDir, BUILD_MANIFEST)),
|
||||
loadManifest<ReactLoadableManifest>(join(distDir, REACT_LOADABLE_MANIFEST)),
|
||||
hasServerComponents
|
||||
? loadManifest<ClientReferenceManifest>(
|
||||
join(distDir, 'server', CLIENT_REFERENCE_MANIFEST + '.json')
|
||||
hasClientManifest
|
||||
? loadJSManifest<ClientReferenceManifest>(
|
||||
join(
|
||||
distDir,
|
||||
'server',
|
||||
'app',
|
||||
pathname.replace(/%5F/g, '_') +
|
||||
'_' +
|
||||
CLIENT_REFERENCE_MANIFEST +
|
||||
'.js'
|
||||
),
|
||||
'__RSC_MANIFEST',
|
||||
pathname.replace(/%5F/g, '_')
|
||||
)
|
||||
: undefined,
|
||||
hasServerComponents
|
||||
isAppPath
|
||||
? loadManifest(
|
||||
join(distDir, 'server', SERVER_REFERENCE_MANIFEST + '.json')
|
||||
).catch(() => null)
|
||||
|
|
|
@ -42,7 +42,6 @@ import {
|
|||
CLIENT_STATIC_FILES_RUNTIME,
|
||||
PRERENDER_MANIFEST,
|
||||
ROUTES_MANIFEST,
|
||||
CLIENT_REFERENCE_MANIFEST,
|
||||
CLIENT_PUBLIC_FILES_PATH,
|
||||
APP_PATHS_MANIFEST,
|
||||
SERVER_DIRECTORY,
|
||||
|
@ -258,13 +257,11 @@ export default class NextNodeServer extends BaseServer {
|
|||
loadComponents({
|
||||
distDir: this.distDir,
|
||||
pathname: '/_document',
|
||||
hasServerComponents: false,
|
||||
isAppPath: false,
|
||||
}).catch(() => {})
|
||||
loadComponents({
|
||||
distDir: this.distDir,
|
||||
pathname: '/_app',
|
||||
hasServerComponents: false,
|
||||
isAppPath: false,
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
@ -968,9 +965,8 @@ export default class NextNodeServer extends BaseServer {
|
|||
renderOpts: RenderOpts
|
||||
): Promise<RenderResult> {
|
||||
// Due to the way we pass data by mutating `renderOpts`, we can't extend the
|
||||
// object here but only updating its `clientReferenceManifest` field.
|
||||
// object here but only updating its `nextFontManifest` field.
|
||||
// https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952
|
||||
renderOpts.clientReferenceManifest = this.clientReferenceManifest
|
||||
renderOpts.nextFontManifest = this.nextFontManifest
|
||||
|
||||
if (this.hasAppDir && renderOpts.isAppPath) {
|
||||
|
@ -1128,7 +1124,6 @@ export default class NextNodeServer extends BaseServer {
|
|||
const components = await loadComponents({
|
||||
distDir: this.distDir,
|
||||
pathname: pagePath,
|
||||
hasServerComponents: !!this.renderOpts.serverComponents,
|
||||
isAppPath,
|
||||
})
|
||||
|
||||
|
@ -1172,15 +1167,6 @@ export default class NextNodeServer extends BaseServer {
|
|||
return requireFontManifest(this.distDir)
|
||||
}
|
||||
|
||||
protected getServerComponentManifest() {
|
||||
if (!this.hasAppDir) return undefined
|
||||
return require(join(
|
||||
this.distDir,
|
||||
'server',
|
||||
CLIENT_REFERENCE_MANIFEST + '.json'
|
||||
))
|
||||
}
|
||||
|
||||
protected getNextFontManifest() {
|
||||
return require(join(this.distDir, 'server', `${NEXT_FONT_MANIFEST}.json`))
|
||||
}
|
||||
|
|
|
@ -165,10 +165,6 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
|
|||
}
|
||||
return prerenderManifest
|
||||
}
|
||||
protected getServerComponentManifest() {
|
||||
return this.serverOptions.webServerConfig.extendRenderOpts
|
||||
.clientReferenceManifest
|
||||
}
|
||||
|
||||
protected getNextFontManifest() {
|
||||
return this.serverOptions.webServerConfig.extendRenderOpts.nextFontManifest
|
||||
|
|
|
@ -56,7 +56,7 @@ export const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__'
|
|||
export const NEXT_BUILTIN_DOCUMENT = '__NEXT_BUILTIN_DOCUMENT__'
|
||||
export const NEXT_CLIENT_SSR_ENTRY_SUFFIX = '.__sc_client__'
|
||||
|
||||
// server/client-reference-manifest
|
||||
// server/[entry]/page_client-reference-manifest.js
|
||||
export const CLIENT_REFERENCE_MANIFEST = 'client-reference-manifest'
|
||||
// server/server-reference-manifest
|
||||
export const SERVER_REFERENCE_MANIFEST = 'server-reference-manifest'
|
||||
|
|
|
@ -477,13 +477,16 @@ createNextDescribe(
|
|||
})
|
||||
|
||||
expect(files).toEqual([
|
||||
'(new)/custom/page_client-reference-manifest.js',
|
||||
'(new)/custom/page.js',
|
||||
'api/draft-mode/route.js',
|
||||
'api/revalidate-path-edge/route.js',
|
||||
'api/revalidate-path-node/route.js',
|
||||
'api/revalidate-tag-edge/route.js',
|
||||
'api/revalidate-tag-node/route.js',
|
||||
'blog/[author]/[slug]/page_client-reference-manifest.js',
|
||||
'blog/[author]/[slug]/page.js',
|
||||
'blog/[author]/page_client-reference-manifest.js',
|
||||
'blog/[author]/page.js',
|
||||
'blog/seb.html',
|
||||
'blog/seb.rsc',
|
||||
|
@ -499,29 +502,45 @@ createNextDescribe(
|
|||
'blog/tim.rsc',
|
||||
'blog/tim/first-post.html',
|
||||
'blog/tim/first-post.rsc',
|
||||
'default-cache/page_client-reference-manifest.js',
|
||||
'default-cache/page.js',
|
||||
'dynamic-error/[id]/page_client-reference-manifest.js',
|
||||
'dynamic-error/[id]/page.js',
|
||||
'dynamic-no-gen-params-ssr/[slug]/page_client-reference-manifest.js',
|
||||
'dynamic-no-gen-params-ssr/[slug]/page.js',
|
||||
'dynamic-no-gen-params/[slug]/page_client-reference-manifest.js',
|
||||
'dynamic-no-gen-params/[slug]/page.js',
|
||||
'fetch-no-cache/page_client-reference-manifest.js',
|
||||
'fetch-no-cache/page.js',
|
||||
'flight/[slug]/[slug2]/page_client-reference-manifest.js',
|
||||
'flight/[slug]/[slug2]/page.js',
|
||||
'force-cache.html',
|
||||
'force-cache.rsc',
|
||||
'force-cache/page_client-reference-manifest.js',
|
||||
'force-cache/page.js',
|
||||
'force-dynamic-catch-all/[slug]/[[...id]]/page_client-reference-manifest.js',
|
||||
'force-dynamic-catch-all/[slug]/[[...id]]/page.js',
|
||||
'force-dynamic-no-prerender/[id]/page_client-reference-manifest.js',
|
||||
'force-dynamic-no-prerender/[id]/page.js',
|
||||
'force-dynamic-prerender/[slug]/page_client-reference-manifest.js',
|
||||
'force-dynamic-prerender/[slug]/page.js',
|
||||
'force-no-store/page_client-reference-manifest.js',
|
||||
'force-no-store/page.js',
|
||||
'force-static/[slug]/page_client-reference-manifest.js',
|
||||
'force-static/[slug]/page.js',
|
||||
'force-static/first.html',
|
||||
'force-static/first.rsc',
|
||||
'force-static/page_client-reference-manifest.js',
|
||||
'force-static/page.js',
|
||||
'force-static/second.html',
|
||||
'force-static/second.rsc',
|
||||
'gen-params-dynamic-revalidate/[slug]/page_client-reference-manifest.js',
|
||||
'gen-params-dynamic-revalidate/[slug]/page.js',
|
||||
'gen-params-dynamic-revalidate/one.html',
|
||||
'gen-params-dynamic-revalidate/one.rsc',
|
||||
'gen-params-dynamic/[slug]/page_client-reference-manifest.js',
|
||||
'gen-params-dynamic/[slug]/page.js',
|
||||
'hooks/use-pathname/[slug]/page_client-reference-manifest.js',
|
||||
'hooks/use-pathname/[slug]/page.js',
|
||||
'hooks/use-pathname/slug.html',
|
||||
'hooks/use-pathname/slug.rsc',
|
||||
|
@ -529,14 +548,19 @@ createNextDescribe(
|
|||
'hooks/use-search-params.rsc',
|
||||
'hooks/use-search-params/force-static.html',
|
||||
'hooks/use-search-params/force-static.rsc',
|
||||
'hooks/use-search-params/force-static/page_client-reference-manifest.js',
|
||||
'hooks/use-search-params/force-static/page.js',
|
||||
'hooks/use-search-params/page_client-reference-manifest.js',
|
||||
'hooks/use-search-params/page.js',
|
||||
'hooks/use-search-params/with-suspense.html',
|
||||
'hooks/use-search-params/with-suspense.rsc',
|
||||
'hooks/use-search-params/with-suspense/page_client-reference-manifest.js',
|
||||
'hooks/use-search-params/with-suspense/page.js',
|
||||
'index.html',
|
||||
'index.rsc',
|
||||
'page_client-reference-manifest.js',
|
||||
'page.js',
|
||||
'partial-gen-params-no-additional-lang/[lang]/[slug]/page_client-reference-manifest.js',
|
||||
'partial-gen-params-no-additional-lang/[lang]/[slug]/page.js',
|
||||
'partial-gen-params-no-additional-lang/en/RAND.html',
|
||||
'partial-gen-params-no-additional-lang/en/RAND.rsc',
|
||||
|
@ -550,6 +574,7 @@ createNextDescribe(
|
|||
'partial-gen-params-no-additional-lang/fr/first.rsc',
|
||||
'partial-gen-params-no-additional-lang/fr/second.html',
|
||||
'partial-gen-params-no-additional-lang/fr/second.rsc',
|
||||
'partial-gen-params-no-additional-slug/[lang]/[slug]/page_client-reference-manifest.js',
|
||||
'partial-gen-params-no-additional-slug/[lang]/[slug]/page.js',
|
||||
'partial-gen-params-no-additional-slug/en/RAND.html',
|
||||
'partial-gen-params-no-additional-slug/en/RAND.rsc',
|
||||
|
@ -563,9 +588,13 @@ createNextDescribe(
|
|||
'partial-gen-params-no-additional-slug/fr/first.rsc',
|
||||
'partial-gen-params-no-additional-slug/fr/second.html',
|
||||
'partial-gen-params-no-additional-slug/fr/second.rsc',
|
||||
'partial-gen-params/[lang]/[slug]/page_client-reference-manifest.js',
|
||||
'partial-gen-params/[lang]/[slug]/page.js',
|
||||
'react-fetch-deduping-edge/page_client-reference-manifest.js',
|
||||
'react-fetch-deduping-edge/page.js',
|
||||
'react-fetch-deduping-node/page_client-reference-manifest.js',
|
||||
'react-fetch-deduping-node/page.js',
|
||||
'response-url/page_client-reference-manifest.js',
|
||||
'response-url/page.js',
|
||||
'route-handler-edge/revalidate-360/route.js',
|
||||
'route-handler/post/route.js',
|
||||
|
@ -574,49 +603,73 @@ createNextDescribe(
|
|||
'route-handler/static-cookies/route.js',
|
||||
'ssg-draft-mode.html',
|
||||
'ssg-draft-mode.rsc',
|
||||
'ssg-draft-mode/[[...route]]/page_client-reference-manifest.js',
|
||||
'ssg-draft-mode/[[...route]]/page.js',
|
||||
'ssg-draft-mode/test-2.html',
|
||||
'ssg-draft-mode/test-2.rsc',
|
||||
'ssg-draft-mode/test.html',
|
||||
'ssg-draft-mode/test.rsc',
|
||||
'ssr-auto/cache-no-store/page_client-reference-manifest.js',
|
||||
'ssr-auto/cache-no-store/page.js',
|
||||
'ssr-auto/fetch-revalidate-zero/page_client-reference-manifest.js',
|
||||
'ssr-auto/fetch-revalidate-zero/page.js',
|
||||
'ssr-forced/page_client-reference-manifest.js',
|
||||
'ssr-forced/page.js',
|
||||
'static-to-dynamic-error-forced/[id]/page_client-reference-manifest.js',
|
||||
'static-to-dynamic-error-forced/[id]/page.js',
|
||||
'static-to-dynamic-error/[id]/page_client-reference-manifest.js',
|
||||
'static-to-dynamic-error/[id]/page.js',
|
||||
'variable-config-revalidate/revalidate-3.html',
|
||||
'variable-config-revalidate/revalidate-3.rsc',
|
||||
'variable-config-revalidate/revalidate-3/page_client-reference-manifest.js',
|
||||
'variable-config-revalidate/revalidate-3/page.js',
|
||||
'variable-revalidate-edge/body/page_client-reference-manifest.js',
|
||||
'variable-revalidate-edge/body/page.js',
|
||||
'variable-revalidate-edge/encoding/page_client-reference-manifest.js',
|
||||
'variable-revalidate-edge/encoding/page.js',
|
||||
'variable-revalidate-edge/no-store/page_client-reference-manifest.js',
|
||||
'variable-revalidate-edge/no-store/page.js',
|
||||
'variable-revalidate-edge/post-method-request/page_client-reference-manifest.js',
|
||||
'variable-revalidate-edge/post-method-request/page.js',
|
||||
'variable-revalidate-edge/post-method/page_client-reference-manifest.js',
|
||||
'variable-revalidate-edge/post-method/page.js',
|
||||
'variable-revalidate-edge/revalidate-3/page_client-reference-manifest.js',
|
||||
'variable-revalidate-edge/revalidate-3/page.js',
|
||||
'variable-revalidate/authorization.html',
|
||||
'variable-revalidate/authorization.rsc',
|
||||
'variable-revalidate/authorization/page_client-reference-manifest.js',
|
||||
'variable-revalidate/authorization/page.js',
|
||||
'variable-revalidate/cookie.html',
|
||||
'variable-revalidate/cookie.rsc',
|
||||
'variable-revalidate/cookie/page_client-reference-manifest.js',
|
||||
'variable-revalidate/cookie/page.js',
|
||||
'variable-revalidate/encoding.html',
|
||||
'variable-revalidate/encoding.rsc',
|
||||
'variable-revalidate/encoding/page_client-reference-manifest.js',
|
||||
'variable-revalidate/encoding/page.js',
|
||||
'variable-revalidate/headers-instance.html',
|
||||
'variable-revalidate/headers-instance.rsc',
|
||||
'variable-revalidate/headers-instance/page_client-reference-manifest.js',
|
||||
'variable-revalidate/headers-instance/page.js',
|
||||
'variable-revalidate/no-store/page_client-reference-manifest.js',
|
||||
'variable-revalidate/no-store/page.js',
|
||||
'variable-revalidate/post-method-request/page_client-reference-manifest.js',
|
||||
'variable-revalidate/post-method-request/page.js',
|
||||
'variable-revalidate/post-method.html',
|
||||
'variable-revalidate/post-method.rsc',
|
||||
'variable-revalidate/post-method/page_client-reference-manifest.js',
|
||||
'variable-revalidate/post-method/page.js',
|
||||
'variable-revalidate/revalidate-3.html',
|
||||
'variable-revalidate/revalidate-3.rsc',
|
||||
'variable-revalidate/revalidate-3/page_client-reference-manifest.js',
|
||||
'variable-revalidate/revalidate-3/page.js',
|
||||
'variable-revalidate/revalidate-360-isr.html',
|
||||
'variable-revalidate/revalidate-360-isr.rsc',
|
||||
'variable-revalidate/revalidate-360-isr/page_client-reference-manifest.js',
|
||||
'variable-revalidate/revalidate-360-isr/page.js',
|
||||
'variable-revalidate/revalidate-360/page_client-reference-manifest.js',
|
||||
'variable-revalidate/revalidate-360/page.js',
|
||||
'variable-revalidate/status-code/page_client-reference-manifest.js',
|
||||
'variable-revalidate/status-code/page.js',
|
||||
])
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ createNextDescribe(
|
|||
({ next, isNextStart }) => {
|
||||
if (isNextStart) {
|
||||
it('should have correct size in build output', async () => {
|
||||
const regex = /(\S+)\s+(\d+\s\w+)\s+(\d+\.\d+\s\w+)/g
|
||||
const regex = /(\S+)\s+([\d.]+\s\w+)\s+([\d.]+\s\w+)/g
|
||||
const matches = [...next.cliOutput.matchAll(regex)]
|
||||
|
||||
const result = matches.reduce((acc, match) => {
|
||||
|
|
|
@ -30,7 +30,13 @@ createNextDescribe(
|
|||
await check(async () => {
|
||||
// Check that the client-side manifest is correct before any requests
|
||||
const clientReferenceManifest = JSON.parse(
|
||||
await next.readFile('.next/server/client-reference-manifest.json')
|
||||
JSON.parse(
|
||||
(
|
||||
await next.readFile(
|
||||
'.next/server/app/page_client-reference-manifest.js'
|
||||
)
|
||||
).match(/]=(.+)$/)[1]
|
||||
)
|
||||
)
|
||||
const clientModulesNames = Object.keys(
|
||||
clientReferenceManifest.clientModules
|
||||
|
@ -546,7 +552,6 @@ createNextDescribe(
|
|||
const files = [
|
||||
'middleware-build-manifest.js',
|
||||
'middleware-manifest.json',
|
||||
'client-reference-manifest.json',
|
||||
]
|
||||
|
||||
let promises = files.map(async (file) => {
|
||||
|
@ -559,6 +564,16 @@ createNextDescribe(
|
|||
})
|
||||
await Promise.all(promises)
|
||||
})
|
||||
|
||||
it('should generate client reference manifest for edge SSR pages', async () => {
|
||||
const buildManifest = JSON.parse(
|
||||
await next.readFile('.next/app-build-manifest.json')
|
||||
)
|
||||
|
||||
expect(buildManifest.pages['/edge/dynamic/page']).toInclude(
|
||||
'server/app/edge/dynamic/page_client-reference-manifest.js'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue