From 2b13c2f7fa6682a9b68b3a86589f2cef9de8d0e0 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 11 Mar 2022 14:33:40 +0100 Subject: [PATCH] Ensure that pages manifest contains pages of both runtimes (#35243) Previously we only run `PagesManifestPlugin` in the Node server runtime, because the Edge target doesn't need it as the web server and middleware SSR loader specially handled pages manifest. This cases entrypoints with the Edge runtime configured being missing from there and this PR fixes it. Part of #31317 and #31506. ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- packages/next/build/webpack-config.ts | 7 +++- .../webpack/plugins/pages-manifest-plugin.ts | 41 +++++++++++++++++-- .../test/runtime.js | 33 ++++++++++++++- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 4e480e73f107..813dbfbdae56 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1439,8 +1439,11 @@ export default async function getBaseWebpackConfig( }), ((isServerless && isServer) || isEdgeRuntime) && new ServerlessPlugin(), isServer && - !isEdgeRuntime && - new PagesManifestPlugin({ serverless: isLikeServerless, dev }), + new PagesManifestPlugin({ + serverless: isLikeServerless, + dev, + isEdgeRuntime, + }), // MiddlewarePlugin should be after DefinePlugin so NEXT_PUBLIC_* // replacement is done before its process.env.* handling (!isServer || isEdgeRuntime) && diff --git a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts index 75a308efc6b8..e562239c5295 100644 --- a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts @@ -4,16 +4,29 @@ import getRouteFromEntrypoint from '../../../server/get-route-from-entrypoint' export type PagesManifest = { [page: string]: string } +let edgeServerPages = {} +let nodeServerPages = {} + // This plugin creates a pages-manifest.json from page entrypoints. // This is used for mapping paths like `/` to `.next/server/static//pages/index.js` when doing SSR // It's also used by next export to provide defaultPathMap export default class PagesManifestPlugin implements webpack.Plugin { serverless: boolean dev: boolean + isEdgeRuntime: boolean - constructor({ serverless, dev }: { serverless: boolean; dev: boolean }) { + constructor({ + serverless, + dev, + isEdgeRuntime, + }: { + serverless: boolean + dev: boolean + isEdgeRuntime: boolean + }) { this.serverless = serverless this.dev = dev + this.isEdgeRuntime = isEdgeRuntime } createAssets(compilation: any, assets: any) { @@ -40,13 +53,33 @@ export default class PagesManifestPlugin implements webpack.Plugin { pages[pagePath] = files[files.length - 1] if (!this.dev) { - pages[pagePath] = pages[pagePath].slice(3) + if (!this.isEdgeRuntime) { + pages[pagePath] = pages[pagePath].slice(3) + } } pages[pagePath] = pages[pagePath].replace(/\\/g, '/') } - assets[`${!this.dev ? '../' : ''}` + PAGES_MANIFEST] = - new sources.RawSource(JSON.stringify(pages, null, 2)) + // This plugin is used by both the Node server and Edge server compilers, + // we need to merge both pages to generate the full manifest. + if (this.isEdgeRuntime) { + edgeServerPages = pages + } else { + nodeServerPages = pages + } + + assets[ + `${!this.dev && !this.isEdgeRuntime ? '../' : ''}` + PAGES_MANIFEST + ] = new sources.RawSource( + JSON.stringify( + { + ...edgeServerPages, + ...nodeServerPages, + }, + null, + 2 + ) + ) } apply(compiler: webpack.Compiler): void { diff --git a/test/integration/react-streaming-and-server-components/test/runtime.js b/test/integration/react-streaming-and-server-components/test/runtime.js index 9258833bc4c4..b166fdb4ded3 100644 --- a/test/integration/react-streaming-and-server-components/test/runtime.js +++ b/test/integration/react-streaming-and-server-components/test/runtime.js @@ -1,6 +1,10 @@ import { renderViaHTTP } from 'next-test-utils' +import { join } from 'path' +import fs from 'fs-extra' -export default async function runtime(context, { runtime }) { +import { distDir } from './utils' + +export default async function runtime(context, { runtime, env }) { if (runtime === 'edge') { it('should support per-page runtime configuration', async () => { const html1 = await renderViaHTTP(context.appPort, '/runtime') @@ -9,4 +13,31 @@ export default async function runtime(context, { runtime }) { expect(html2).toContain('Runtime: Node.js') }) } + if (runtime === 'edge' && env === 'prod') { + it('should include entrypoints from both runtimes in pages manifest', async () => { + const distServerDir = join(distDir, 'server') + const pagesManifest = await fs.readJSON( + join(distServerDir, 'pages-manifest.json') + ) + + for (const key of [ + // Defaults: + '/_app', + '/_error', + '/_document', + // Special: + '/404', + // API routes: + '/api/ping', + // Edge runtime pages: + '/streaming', + '/streaming-rsc', + // Node runtime pages: + '/runtime', + '/runtime-rsc', + ]) { + expect(key in pagesManifest).toBeTruthy() + } + }) + } }