From ce86fba163a3198d2f5537d4a463eb340d85b16d Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 1 Feb 2022 22:07:32 +0100 Subject: [PATCH 01/11] page plugin --- .../loaders/next-flight-server-loader.ts | 1 - .../plugins/functions-manifest-plugin.ts | 29 +++++++++++++++++++ .../app/pages/index.server.js | 4 +++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 8d5b84927e235c8..6d1aaa32c22e60e 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -163,6 +163,5 @@ export default async function transformSource( : '' const transformed = transformedSource + '\n' + noop + '\n' + defaultExportNoop - return transformed } diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index 3c68bfb5de16faf..947edc7f2c957be 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -59,6 +59,35 @@ export default class FunctionsManifestPlugin { } apply(compiler: webpack5.Compiler) { + const handler = (parser: any) => { + parser.hooks.evaluate + .for('config') + .tap(PLUGIN_NAME, (expression: any) => { + console.log('evaluate expression', expression) + }) + + parser.hooks.exportDeclaration + // .for('config') + .tap(PLUGIN_NAME, (expression: any) => { + console.log('exportDeclaration expression', expression) + }) + + parser.hooks.export + // .for('config') + .tap(PLUGIN_NAME, (expression: any) => { + console.log('export expression', expression) + }) + + parser.hooks.exportSpecifier.tap(PLUGIN_NAME, (expression: any) => { + console.log('exportSpecifier', expression) + }) + } + + compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (factory) => { + factory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, handler) + factory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, handler) + }) + collectAssets(compiler, this.createAssets.bind(this), { dev: this.dev, pluginName: PLUGIN_NAME, diff --git a/test/integration/react-streaming-and-server-components/app/pages/index.server.js b/test/integration/react-streaming-and-server-components/app/pages/index.server.js index 84efc4ddaefe66b..ee88353bb0b5fa6 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/index.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/index.server.js @@ -27,3 +27,7 @@ export function getServerSideProps({ req }) { }, } } + +export const config = { + runtime: 'web', +} From eca68d120bd88d33a2a4335ded5ee112677f8bd1 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 2 Feb 2022 22:34:50 +0100 Subject: [PATCH 02/11] Parse page config runtime option for functions manifest --- .../plugins/functions-manifest-plugin.ts | 74 ++++++++++++------- .../webpack/plugins/middleware-plugin.ts | 25 ++++--- .../plugins/next-drop-client-page-plugin.ts | 41 +++++----- 3 files changed, 88 insertions(+), 52 deletions(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index 947edc7f2c957be..ebb44859f822272 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -1,6 +1,7 @@ import { sources, webpack5 } from 'next/dist/compiled/webpack/webpack' import { collectAssets, getEntrypointInfo } from './middleware-plugin' import { FUNCTIONS_MANIFEST } from '../../../shared/lib/constants' +import { findEntryModule } from './next-drop-client-page-plugin' const PLUGIN_NAME = 'FunctionsManifestPlugin' export interface FunctionsManifest { @@ -20,6 +21,7 @@ export interface FunctionsManifest { export default class FunctionsManifestPlugin { dev: boolean webServerRuntime: boolean + pagesRuntime: Map constructor({ dev, @@ -30,6 +32,7 @@ export default class FunctionsManifestPlugin { }) { this.dev = dev this.webServerRuntime = webServerRuntime + this.pagesRuntime = new Map() } createAssets( @@ -45,8 +48,12 @@ export default class FunctionsManifestPlugin { const infos = getEntrypointInfo(compilation, envPerRoute, webServerRuntime) infos.forEach((info) => { - functionsManifest.pages[info.page] = { - runtime: 'web', + const { page } = info + // TODO: use web for pages configured runtime: "web"; + // Not assign if it's nodejs runtime, project configured node version is used instead + const runtime = this.pagesRuntime.get(page) || 'web' + functionsManifest.pages[page] = { + runtime, ...info, } }) @@ -59,34 +66,49 @@ export default class FunctionsManifestPlugin { } apply(compiler: webpack5.Compiler) { - const handler = (parser: any) => { - parser.hooks.evaluate - .for('config') - .tap(PLUGIN_NAME, (expression: any) => { - console.log('evaluate expression', expression) - }) + const handler = (parser: webpack5.javascript.JavascriptParser) => { + parser.hooks.exportSpecifier.tap( + PLUGIN_NAME, + (statement: any, _identifierName: string, exportName: string) => { + const entryModule = findEntryModule( + parser.state.compilation, + parser.state.module + ) + if (!entryModule) { + return + } - parser.hooks.exportDeclaration - // .for('config') - .tap(PLUGIN_NAME, (expression: any) => { - console.log('exportDeclaration expression', expression) - }) + const { declaration } = statement + if (exportName === 'config') { + const varDecl = declaration.declarations[0] + const init = varDecl.init.properties[0] + const runtime = init.value.value - parser.hooks.export - // .for('config') - .tap(PLUGIN_NAME, (expression: any) => { - console.log('export expression', expression) - }) - - parser.hooks.exportSpecifier.tap(PLUGIN_NAME, (expression: any) => { - console.log('exportSpecifier', expression) - }) + // @ts-ignore buildInfo exists on Module + entryModule.buildInfo.NEXT_runtime = runtime + } + } + ) } - compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (factory) => { - factory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, handler) - factory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, handler) - }) + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation: any, { normalModuleFactory: factory }: any) => { + factory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, handler) + factory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, handler) + + compilation.hooks.seal.tap(PLUGIN_NAME, () => { + for (const [name, entryData] of compilation.entries) { + for (const dependency of entryData.dependencies) { + // @ts-ignore TODO: webpack 5 types + const module = compilation.moduleGraph.getModule(dependency) + const runtime = module?.buildInfo?.NEXT_runtime + this.pagesRuntime.set(name, runtime) + } + } + }) + } + ) collectAssets(compiler, this.createAssets.bind(this), { dev: this.dev, diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index a97028d29020a33..c53acd6b316e4aa 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -38,6 +38,17 @@ const middlewareManifest: MiddlewareManifest = { version: 1, } +function getPageFromEntrypoint(entrypoint: any) { + const ssrEntryInfo = ssrEntries.get(entrypoint.name) + const result = MIDDLEWARE_FULL_ROUTE_REGEX.exec(entrypoint.name) + const page = result + ? `/${result[1]}` + : ssrEntryInfo + ? entrypoint.name.slice('pages'.length).replace(/\/index$/, '') || '/' + : null + return page +} + export function getEntrypointInfo( compilation: webpack5.Compilation, envPerRoute: Map, @@ -47,19 +58,15 @@ export function getEntrypointInfo( const infos = [] for (const entrypoint of entrypoints.values()) { if (!entrypoint.name) continue - const result = MIDDLEWARE_FULL_ROUTE_REGEX.exec(entrypoint.name) + const ssrEntryInfo = ssrEntries.get(entrypoint.name) if (ssrEntryInfo && !webServerRuntime) continue if (!ssrEntryInfo && webServerRuntime) continue - const location = result - ? `/${result[1]}` - : ssrEntryInfo - ? entrypoint.name.slice('pages'.length).replace(/\/index$/, '') || '/' - : null + const page = getPageFromEntrypoint(entrypoint) - if (!location) { + if (!page) { continue } @@ -82,8 +89,8 @@ export function getEntrypointInfo( env: envPerRoute.get(entrypoint.name) || [], files, name: entrypoint.name, - page: location, - regexp: getMiddlewareRegex(location, !ssrEntryInfo).namedRegex!, + page, + regexp: getMiddlewareRegex(page, !ssrEntryInfo).namedRegex!, }) } return infos diff --git a/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts b/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts index ac942e0953d4b1e..3e969b0ce54610b 100644 --- a/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts +++ b/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts @@ -1,4 +1,4 @@ -import { webpack } from 'next/dist/compiled/webpack/webpack' +import { webpack, webpack5 } from 'next/dist/compiled/webpack/webpack' import { STRING_LITERAL_DROP_BUNDLE } from '../../../shared/lib/constants' export const ampFirstEntryNamesMap: WeakMap< @@ -8,6 +8,25 @@ export const ampFirstEntryNamesMap: WeakMap< const PLUGIN_NAME = 'DropAmpFirstPagesPlugin' +export function findEntryModule( + compilation: webpack5.Compilation, + mod: any +): webpack.compilation.Module | null { + const queue = new Set([mod]) + for (const module of queue) { + // @ts-ignore TODO: webpack 5 types + const incomingConnections = + compilation.moduleGraph.getIncomingConnections(module) + + for (const incomingConnection of incomingConnections) { + if (!incomingConnection.originModule) return module + queue.add(incomingConnection.originModule) + } + } + + return null +} + // Prevents outputting client pages when they are not needed export class DropClientPage implements webpack.Plugin { ampPages = new Set() @@ -17,25 +36,13 @@ export class DropClientPage implements webpack.Plugin { PLUGIN_NAME, (compilation: any, { normalModuleFactory }: any) => { // Recursively look up the issuer till it ends up at the root - function findEntryModule(mod: any): webpack.compilation.Module | null { - const queue = new Set([mod]) - for (const module of queue) { - // @ts-ignore TODO: webpack 5 types - const incomingConnections = - compilation.moduleGraph.getIncomingConnections(module) - - for (const incomingConnection of incomingConnections) { - if (!incomingConnection.originModule) return module - queue.add(incomingConnection.originModule) - } - } - - return null - } function handler(parser: any) { function markAsAmpFirst() { - const entryModule = findEntryModule(parser.state.module) + const entryModule = findEntryModule( + compilation, + parser.state.module + ) if (!entryModule) { return From 2ae09a78925cb98e50235ae3cb7d1daffa804d1a Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 3 Feb 2022 13:00:14 +0100 Subject: [PATCH 03/11] mapping runtime --- .../plugins/functions-manifest-plugin.ts | 31 ++++++++++++++----- .../webpack/plugins/middleware-plugin.ts | 10 +++--- .../app/pages/index.server.js | 2 +- .../test/index.test.js | 6 ++-- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index ebb44859f822272..bbf7d209c4312ce 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -1,6 +1,10 @@ import { sources, webpack5 } from 'next/dist/compiled/webpack/webpack' -import { collectAssets, getEntrypointInfo } from './middleware-plugin' import { FUNCTIONS_MANIFEST } from '../../../shared/lib/constants' +import { + collectAssets, + getEntrypointInfo, + getPageFromPath, +} from './middleware-plugin' import { findEntryModule } from './next-drop-client-page-plugin' const PLUGIN_NAME = 'FunctionsManifestPlugin' @@ -8,7 +12,7 @@ export interface FunctionsManifest { version: 1 pages: { [page: string]: { - runtime: string + runtime?: string env: string[] files: string[] name: string @@ -49,11 +53,13 @@ export default class FunctionsManifestPlugin { const infos = getEntrypointInfo(compilation, envPerRoute, webServerRuntime) infos.forEach((info) => { const { page } = info - // TODO: use web for pages configured runtime: "web"; - // Not assign if it's nodejs runtime, project configured node version is used instead + // TODO: use global default runtime instead of 'web' + // console.log(Array.from(this.pagesRuntime.entries())) const runtime = this.pagesRuntime.get(page) || 'web' + // console.log('page:runtime', page, runtime) functionsManifest.pages[page] = { - runtime, + // Not assign if it's nodejs runtime, project configured node version is used instead + ...(runtime !== 'nodejs' && { runtime }), ...info, } }) @@ -99,12 +105,23 @@ export default class FunctionsManifestPlugin { compilation.hooks.seal.tap(PLUGIN_NAME, () => { for (const [name, entryData] of compilation.entries) { + let runtime for (const dependency of entryData.dependencies) { // @ts-ignore TODO: webpack 5 types const module = compilation.moduleGraph.getModule(dependency) - const runtime = module?.buildInfo?.NEXT_runtime - this.pagesRuntime.set(name, runtime) + const parentModule = + compilation.moduleGraph.getParentModule(dependency) + runtime = module?.buildInfo?.NEXT_runtime + console.log( + 'module?.buildInfo', + name, + module?.buildInfo?.NEXT_runtime, + parentModule?.buildInfo?.NEXT_runtime + ) + if (runtime) break } + const page = getPageFromPath(name) + if (page && runtime) this.pagesRuntime.set(name, runtime) } }) } diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index c53acd6b316e4aa..775b42fb39c47dd 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -38,13 +38,13 @@ const middlewareManifest: MiddlewareManifest = { version: 1, } -function getPageFromEntrypoint(entrypoint: any) { - const ssrEntryInfo = ssrEntries.get(entrypoint.name) - const result = MIDDLEWARE_FULL_ROUTE_REGEX.exec(entrypoint.name) +export function getPageFromPath(pagePath: string) { + const ssrEntryInfo = ssrEntries.get(pagePath) + const result = MIDDLEWARE_FULL_ROUTE_REGEX.exec(pagePath) const page = result ? `/${result[1]}` : ssrEntryInfo - ? entrypoint.name.slice('pages'.length).replace(/\/index$/, '') || '/' + ? pagePath.slice('pages'.length).replace(/\/index$/, '') || '/' : null return page } @@ -64,7 +64,7 @@ export function getEntrypointInfo( if (ssrEntryInfo && !webServerRuntime) continue if (!ssrEntryInfo && webServerRuntime) continue - const page = getPageFromEntrypoint(entrypoint) + const page = getPageFromPath(entrypoint.name) if (!page) { continue diff --git a/test/integration/react-streaming-and-server-components/app/pages/index.server.js b/test/integration/react-streaming-and-server-components/app/pages/index.server.js index ee88353bb0b5fa6..bd2a010ee3d8d14 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/index.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/index.server.js @@ -29,5 +29,5 @@ export function getServerSideProps({ req }) { } export const config = { - runtime: 'web', + runtime: 'nodejs', } diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 8ab7f16a2b3695a..218d0e6b135d0a9 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -236,9 +236,11 @@ describe('Functions manifest', () => { const paths = ['/', '/next-api/link', '/routes/[dynamic]'] paths.forEach((path) => { + const { runtime, files } = pages[path] expect(pageNames).toContain(path) - expect(pages[path].runtime).toBe('web') - expect(pages[path].files.every((f) => f.startsWith('server/'))).toBe(true) + console.log(path, runtime) + // expect(runtime).toBe(path === '/' ? undefined : 'web') + expect(files.every((f) => f.startsWith('server/'))).toBe(true) }) expect(content.version).toBe(1) From 8fa2c8940c0a015f82e2ae7e2d1d69149fc59493 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 3 Feb 2022 16:13:20 +0100 Subject: [PATCH 04/11] use rawReuqest to build page path --- packages/next/build/entries.ts | 18 +++--- packages/next/build/webpack-config.ts | 7 ++- .../plugins/functions-manifest-plugin.ts | 56 +++++++++---------- .../webpack/plugins/middleware-plugin.ts | 2 +- .../test/index.test.js | 4 +- 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 053944e5d0bc22f..2cf6a12f3a3a295 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -25,6 +25,12 @@ export type PagesMapping = { [page: string]: string } +export function getPageKeyFromPath(pagePath: string, extensions: string[]) { + let page = pagePath.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '') + page = page.replace(/\\/g, '/').replace(/\/index$/, '') + return page === '' ? '/' : page +} + export function createPagesMapping( pagePaths: string[], extensions: string[], @@ -47,20 +53,14 @@ export function createPagesMapping( const pages: PagesMapping = pagePaths.reduce( (result: PagesMapping, pagePath): PagesMapping => { - let page = pagePath.replace( - new RegExp(`\\.+(${extensions.join('|')})$`), - '' - ) - if (hasServerComponents && /\.client$/.test(page)) { + const pageKey = getPageKeyFromPath(pagePath, extensions) + + if (hasServerComponents && /\.client$/.test(pageKey)) { // Assume that if there's a Client Component, that there is // a matching Server Component that will map to the page. return result } - page = page.replace(/\\/g, '/').replace(/\/index$/, '') - - const pageKey = page === '' ? '/' : page - if (pageKey in result) { warn( `Duplicate page detected. ${chalk.cyan( diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 2c62c2d34243c60..db1ea164201caa3 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1451,7 +1451,12 @@ export default async function getBaseWebpackConfig( new MiddlewarePlugin({ dev, webServerRuntime }), process.env.ENABLE_FILE_SYSTEM_API === '1' && webServerRuntime && - new FunctionsManifestPlugin({ dev, webServerRuntime }), + new FunctionsManifestPlugin({ + dev, + pagesDir, + webServerRuntime, + pageExtensions: config.pageExtensions, + }), isServer && new NextJsSsrImportPlugin(), !isServer && new BuildManifestPlugin({ diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index bbf7d209c4312ce..2d18804daae9299 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -1,11 +1,8 @@ import { sources, webpack5 } from 'next/dist/compiled/webpack/webpack' +import { normalizePagePath } from '../../../server/normalize-page-path' import { FUNCTIONS_MANIFEST } from '../../../shared/lib/constants' -import { - collectAssets, - getEntrypointInfo, - getPageFromPath, -} from './middleware-plugin' -import { findEntryModule } from './next-drop-client-page-plugin' +import { getPageKeyFromPath } from '../../entries' +import { collectAssets, getEntrypointInfo } from './middleware-plugin' const PLUGIN_NAME = 'FunctionsManifestPlugin' export interface FunctionsManifest { @@ -24,18 +21,26 @@ export interface FunctionsManifest { export default class FunctionsManifestPlugin { dev: boolean + pagesDir: string + pageExtensions: string[] webServerRuntime: boolean pagesRuntime: Map constructor({ dev, + pagesDir, + pageExtensions, webServerRuntime, }: { dev: boolean + pagesDir: string + pageExtensions: string[] webServerRuntime: boolean }) { this.dev = dev + this.pagesDir = pagesDir this.webServerRuntime = webServerRuntime + this.pageExtensions = pageExtensions this.pagesRuntime = new Map() } @@ -54,9 +59,7 @@ export default class FunctionsManifestPlugin { infos.forEach((info) => { const { page } = info // TODO: use global default runtime instead of 'web' - // console.log(Array.from(this.pagesRuntime.entries())) const runtime = this.pagesRuntime.get(page) || 'web' - // console.log('page:runtime', page, runtime) functionsManifest.pages[page] = { // Not assign if it's nodejs runtime, project configured node version is used instead ...(runtime !== 'nodejs' && { runtime }), @@ -76,14 +79,6 @@ export default class FunctionsManifestPlugin { parser.hooks.exportSpecifier.tap( PLUGIN_NAME, (statement: any, _identifierName: string, exportName: string) => { - const entryModule = findEntryModule( - parser.state.compilation, - parser.state.module - ) - if (!entryModule) { - return - } - const { declaration } = statement if (exportName === 'config') { const varDecl = declaration.declarations[0] @@ -91,7 +86,7 @@ export default class FunctionsManifestPlugin { const runtime = init.value.value // @ts-ignore buildInfo exists on Module - entryModule.buildInfo.NEXT_runtime = runtime + parser.state.module.buildInfo.NEXT_runtime = runtime } } ) @@ -104,24 +99,25 @@ export default class FunctionsManifestPlugin { factory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, handler) compilation.hooks.seal.tap(PLUGIN_NAME, () => { - for (const [name, entryData] of compilation.entries) { + for (const [_name, entryData] of compilation.entries) { let runtime for (const dependency of entryData.dependencies) { // @ts-ignore TODO: webpack 5 types const module = compilation.moduleGraph.getModule(dependency) - const parentModule = - compilation.moduleGraph.getParentModule(dependency) - runtime = module?.buildInfo?.NEXT_runtime - console.log( - 'module?.buildInfo', - name, - module?.buildInfo?.NEXT_runtime, - parentModule?.buildInfo?.NEXT_runtime - ) - if (runtime) break + const outgoingConnections = + compilation.moduleGraph.getOutgoingConnectionsByModule(module) + const entryModules = outgoingConnections.keys() + for (const mod of entryModules) { + runtime = mod?.buildInfo?.NEXT_runtime + if (runtime) { + const normalizedPagePath = normalizePagePath(mod.userRequest) + const pagePath = normalizedPagePath.replace(this.pagesDir, '') + const page = getPageKeyFromPath(pagePath, this.pageExtensions) + this.pagesRuntime.set(page, runtime) + break + } + } } - const page = getPageFromPath(name) - if (page && runtime) this.pagesRuntime.set(name, runtime) } }) } diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 775b42fb39c47dd..c8f7bbf1e53ba47 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -38,7 +38,7 @@ const middlewareManifest: MiddlewareManifest = { version: 1, } -export function getPageFromPath(pagePath: string) { +function getPageFromPath(pagePath: string) { const ssrEntryInfo = ssrEntries.get(pagePath) const result = MIDDLEWARE_FULL_ROUTE_REGEX.exec(pagePath) const page = result diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 218d0e6b135d0a9..25d0128bca2eeda 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -238,8 +238,8 @@ describe('Functions manifest', () => { paths.forEach((path) => { const { runtime, files } = pages[path] expect(pageNames).toContain(path) - console.log(path, runtime) - // expect(runtime).toBe(path === '/' ? undefined : 'web') + // Runtime of page `/` is undefined since it's configured as nodejs. + expect(runtime).toBe(path === '/' ? undefined : 'web') expect(files.every((f) => f.startsWith('server/'))).toBe(true) }) From cf1d0cc43d5998ee6d8f9e70b9000671c8713548 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 3 Feb 2022 16:19:43 +0100 Subject: [PATCH 05/11] revert unused changes --- packages/next/build/entries.ts | 4 +- .../loaders/next-flight-server-loader.ts | 1 + .../webpack/plugins/middleware-plugin.ts | 4 +- .../plugins/next-drop-client-page-plugin.ts | 41 ++++++++----------- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 2cf6a12f3a3a295..133b2f49ddae127 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -25,7 +25,7 @@ export type PagesMapping = { [page: string]: string } -export function getPageKeyFromPath(pagePath: string, extensions: string[]) { +export function getPageFromPath(pagePath: string, extensions: string[]) { let page = pagePath.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '') page = page.replace(/\\/g, '/').replace(/\/index$/, '') return page === '' ? '/' : page @@ -53,7 +53,7 @@ export function createPagesMapping( const pages: PagesMapping = pagePaths.reduce( (result: PagesMapping, pagePath): PagesMapping => { - const pageKey = getPageKeyFromPath(pagePath, extensions) + const pageKey = getPageFromPath(pagePath, extensions) if (hasServerComponents && /\.client$/.test(pageKey)) { // Assume that if there's a Client Component, that there is diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 6d1aaa32c22e60e..8d5b84927e235c8 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -163,5 +163,6 @@ export default async function transformSource( : '' const transformed = transformedSource + '\n' + noop + '\n' + defaultExportNoop + return transformed } diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index c8f7bbf1e53ba47..07c1710528eaf7c 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -38,7 +38,7 @@ const middlewareManifest: MiddlewareManifest = { version: 1, } -function getPageFromPath(pagePath: string) { +function getPageFromEntrypointName(pagePath: string) { const ssrEntryInfo = ssrEntries.get(pagePath) const result = MIDDLEWARE_FULL_ROUTE_REGEX.exec(pagePath) const page = result @@ -64,7 +64,7 @@ export function getEntrypointInfo( if (ssrEntryInfo && !webServerRuntime) continue if (!ssrEntryInfo && webServerRuntime) continue - const page = getPageFromPath(entrypoint.name) + const page = getPageFromEntrypointName(entrypoint.name) if (!page) { continue diff --git a/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts b/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts index 3e969b0ce54610b..ac942e0953d4b1e 100644 --- a/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts +++ b/packages/next/build/webpack/plugins/next-drop-client-page-plugin.ts @@ -1,4 +1,4 @@ -import { webpack, webpack5 } from 'next/dist/compiled/webpack/webpack' +import { webpack } from 'next/dist/compiled/webpack/webpack' import { STRING_LITERAL_DROP_BUNDLE } from '../../../shared/lib/constants' export const ampFirstEntryNamesMap: WeakMap< @@ -8,25 +8,6 @@ export const ampFirstEntryNamesMap: WeakMap< const PLUGIN_NAME = 'DropAmpFirstPagesPlugin' -export function findEntryModule( - compilation: webpack5.Compilation, - mod: any -): webpack.compilation.Module | null { - const queue = new Set([mod]) - for (const module of queue) { - // @ts-ignore TODO: webpack 5 types - const incomingConnections = - compilation.moduleGraph.getIncomingConnections(module) - - for (const incomingConnection of incomingConnections) { - if (!incomingConnection.originModule) return module - queue.add(incomingConnection.originModule) - } - } - - return null -} - // Prevents outputting client pages when they are not needed export class DropClientPage implements webpack.Plugin { ampPages = new Set() @@ -36,13 +17,25 @@ export class DropClientPage implements webpack.Plugin { PLUGIN_NAME, (compilation: any, { normalModuleFactory }: any) => { // Recursively look up the issuer till it ends up at the root + function findEntryModule(mod: any): webpack.compilation.Module | null { + const queue = new Set([mod]) + for (const module of queue) { + // @ts-ignore TODO: webpack 5 types + const incomingConnections = + compilation.moduleGraph.getIncomingConnections(module) + + for (const incomingConnection of incomingConnections) { + if (!incomingConnection.originModule) return module + queue.add(incomingConnection.originModule) + } + } + + return null + } function handler(parser: any) { function markAsAmpFirst() { - const entryModule = findEntryModule( - compilation, - parser.state.module - ) + const entryModule = findEntryModule(parser.state.module) if (!entryModule) { return From 4bf49fc956452a4cf429cab08e64a1b3f4697d79 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 3 Feb 2022 16:26:40 +0100 Subject: [PATCH 06/11] detect if edge --- .../build/webpack/plugins/functions-manifest-plugin.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index 2d18804daae9299..063046b77ec341f 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -1,7 +1,7 @@ import { sources, webpack5 } from 'next/dist/compiled/webpack/webpack' import { normalizePagePath } from '../../../server/normalize-page-path' import { FUNCTIONS_MANIFEST } from '../../../shared/lib/constants' -import { getPageKeyFromPath } from '../../entries' +import { getPageFromPath } from '../../entries' import { collectAssets, getEntrypointInfo } from './middleware-plugin' const PLUGIN_NAME = 'FunctionsManifestPlugin' @@ -59,10 +59,12 @@ export default class FunctionsManifestPlugin { infos.forEach((info) => { const { page } = info // TODO: use global default runtime instead of 'web' - const runtime = this.pagesRuntime.get(page) || 'web' + const pageRuntime = this.pagesRuntime.get(page) + const isWebRuntime = + pageRuntime === 'edge' || (this.webServerRuntime && !pageRuntime) functionsManifest.pages[page] = { // Not assign if it's nodejs runtime, project configured node version is used instead - ...(runtime !== 'nodejs' && { runtime }), + ...(isWebRuntime && { runtime: 'web' }), ...info, } }) @@ -112,7 +114,7 @@ export default class FunctionsManifestPlugin { if (runtime) { const normalizedPagePath = normalizePagePath(mod.userRequest) const pagePath = normalizedPagePath.replace(this.pagesDir, '') - const page = getPageKeyFromPath(pagePath, this.pageExtensions) + const page = getPageFromPath(pagePath, this.pageExtensions) this.pagesRuntime.set(page, runtime) break } From bb522d8c3ded0f859744d9fcdf1457c1af92f181 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 3 Feb 2022 16:54:27 +0100 Subject: [PATCH 07/11] fix lint --- .../webpack/plugins/functions-manifest-plugin.ts | 12 ++++++++---- .../app/components/foo.client.js | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index 063046b77ec341f..cd7bd60ea0eae57 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -96,22 +96,26 @@ export default class FunctionsManifestPlugin { compiler.hooks.compilation.tap( PLUGIN_NAME, - (compilation: any, { normalModuleFactory: factory }: any) => { + ( + compilation: webpack5.Compilation, + { normalModuleFactory: factory }: any + ) => { factory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, handler) factory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, handler) compilation.hooks.seal.tap(PLUGIN_NAME, () => { - for (const [_name, entryData] of compilation.entries) { - let runtime + for (const entryData of compilation.entries.values()) { for (const dependency of entryData.dependencies) { // @ts-ignore TODO: webpack 5 types const module = compilation.moduleGraph.getModule(dependency) const outgoingConnections = compilation.moduleGraph.getOutgoingConnectionsByModule(module) + if (!outgoingConnections) return const entryModules = outgoingConnections.keys() for (const mod of entryModules) { - runtime = mod?.buildInfo?.NEXT_runtime + const runtime = mod?.buildInfo?.NEXT_runtime if (runtime) { + // @ts-ignore: TODO: webpack 5 types const normalizedPagePath = normalizePagePath(mod.userRequest) const pagePath = normalizedPagePath.replace(this.pagesDir, '') const page = getPageFromPath(pagePath, this.pageExtensions) diff --git a/test/integration/react-streaming-and-server-components/app/components/foo.client.js b/test/integration/react-streaming-and-server-components/app/components/foo.client.js index 0e28745e7d8f3b9..30d3854497926ef 100644 --- a/test/integration/react-streaming-and-server-components/app/components/foo.client.js +++ b/test/integration/react-streaming-and-server-components/app/components/foo.client.js @@ -1,3 +1,5 @@ export default function foo() { return 'foo.client' } + +export const config = 'this is not page config' From ca49e924f8c679b30de0764396b8f9c9769f4d43 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 3 Feb 2022 19:14:39 +0100 Subject: [PATCH 08/11] remove next cache --- .../react-streaming-and-server-components/test/index.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 25d0128bca2eeda..3c410b997c5be01 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -221,6 +221,7 @@ describe('Functions manifest', () => { 'server', 'functions-manifest.json' ) + await fs.remove(join(appDir, '.next')) expect(fs.existsSync(functionsManifestPath)).toBe(false) }) it('should contain rsc paths in functions manifest', async () => { @@ -230,7 +231,7 @@ describe('Functions manifest', () => { 'server', 'functions-manifest.json' ) - const content = JSON.parse(await fs.readFile(functionsManifestPath, 'utf8')) + const content = JSON.parse(fs.readFileSync(functionsManifestPath, 'utf8')) const { pages } = content const pageNames = Object.keys(pages) From d64a7e9fcba23bfe30e857aabe71e7bbd7bb1932 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 4 Feb 2022 13:01:00 +0100 Subject: [PATCH 09/11] filter config properties --- .../webpack/plugins/functions-manifest-plugin.ts | 13 +++++++++++-- .../app/pages/index.server.js | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index cd7bd60ea0eae57..21b3c8f70eb647b 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -84,8 +84,17 @@ export default class FunctionsManifestPlugin { const { declaration } = statement if (exportName === 'config') { const varDecl = declaration.declarations[0] - const init = varDecl.init.properties[0] - const runtime = init.value.value + const { properties } = varDecl.init + if (!properties) return + const prop = properties.find( + (prop: any) => prop.key.name === 'runtime' + ) + if (!prop) return + const runtime = prop.value.value + if (!['nodejs', 'edge'].includes(runtime)) + throw new Error( + `The runtime option can only be 'nodejs' or 'edge'` + ) // @ts-ignore buildInfo exists on Module parser.state.module.buildInfo.NEXT_runtime = runtime diff --git a/test/integration/react-streaming-and-server-components/app/pages/index.server.js b/test/integration/react-streaming-and-server-components/app/pages/index.server.js index bd2a010ee3d8d14..4874b99a3422821 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/index.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/index.server.js @@ -29,5 +29,7 @@ export function getServerSideProps({ req }) { } export const config = { + amp: false, + unstable_runtimeJS: false, runtime: 'nodejs', } From fdef94e4a15dabd23b009a314c95885dec7c615f Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 4 Feb 2022 13:24:55 +0100 Subject: [PATCH 10/11] only parse page config --- .../webpack/plugins/functions-manifest-plugin.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index 21b3c8f70eb647b..635c9c4a4239715 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -1,3 +1,4 @@ +import { relative } from 'path' import { sources, webpack5 } from 'next/dist/compiled/webpack/webpack' import { normalizePagePath } from '../../../server/normalize-page-path' import { FUNCTIONS_MANIFEST } from '../../../shared/lib/constants' @@ -19,6 +20,10 @@ export interface FunctionsManifest { } } +function containsPath(outer: string, inner: string) { + const rel = relative(outer, inner) + return !rel.startsWith('../') && rel !== '..' +} export default class FunctionsManifestPlugin { dev: boolean pagesDir: string @@ -81,11 +86,16 @@ export default class FunctionsManifestPlugin { parser.hooks.exportSpecifier.tap( PLUGIN_NAME, (statement: any, _identifierName: string, exportName: string) => { + const { resource } = parser.state.module + const isPagePath = containsPath(this.pagesDir, resource) + // Only parse exported config in pages + if (!isPagePath) { + return + } const { declaration } = statement if (exportName === 'config') { const varDecl = declaration.declarations[0] const { properties } = varDecl.init - if (!properties) return const prop = properties.find( (prop: any) => prop.key.name === 'runtime' ) From 6b994e08f5971fabd2b50c46ebc53ca20ef15d38 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 4 Feb 2022 20:09:36 +0100 Subject: [PATCH 11/11] fix lint --- .../next/build/webpack/plugins/functions-manifest-plugin.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts index 635c9c4a4239715..6e27b84d5ab72b4 100644 --- a/packages/next/build/webpack/plugins/functions-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/functions-manifest-plugin.ts @@ -96,9 +96,7 @@ export default class FunctionsManifestPlugin { if (exportName === 'config') { const varDecl = declaration.declarations[0] const { properties } = varDecl.init - const prop = properties.find( - (prop: any) => prop.key.name === 'runtime' - ) + const prop = properties.find((p: any) => p.key.name === 'runtime') if (!prop) return const runtime = prop.value.value if (!['nodejs', 'edge'].includes(runtime))