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:
Shu Ding 2023-07-10 09:48:03 +02:00 committed by GitHub
parent c68c4bdc9f
commit 990c58c5ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 274 additions and 125 deletions

View file

@ -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,

View file

@ -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,
})

View file

@ -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

View file

@ -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,
},

View file

@ -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)

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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,

View file

@ -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 = {

View file

@ -1247,6 +1247,10 @@ export async function renderToHTMLOrFlight(
rscChunks: [],
}
if (!clientReferenceManifest) {
console.log(req.url)
}
const validateRootLayout = dev
? {
validateRootLayout: {

View file

@ -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

View file

@ -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

View file

@ -63,7 +63,6 @@ export async function loadStaticPaths({
const components = await loadComponents({
distDir,
pathname: originalAppPath || pathname,
hasServerComponents: false,
isAppPath: !!isAppPath,
})

View file

@ -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)

View file

@ -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`))
}

View file

@ -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

View file

@ -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'

View file

@ -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',
])
})

View file

@ -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) => {

View file

@ -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'
)
})
}
}
)