From 081f960a161aeb37416fedf3fbb820ae91aa6888 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 29 Mar 2022 23:45:24 +0200 Subject: [PATCH 1/5] Only resolve page runtime for react 18 and error on read failure --- packages/next/build/entries.ts | 27 +++++++++---------- packages/next/server/dev/hot-reloader.ts | 7 +++-- packages/next/server/dev/next-dev-server.ts | 10 ++++--- .../server/dev/on-demand-entry-handler.ts | 11 +++++--- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 9cfbb276b8d4..fe418f9bc861 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -112,14 +112,9 @@ export async function getPageRuntime( return cached[1] } - let pageContent: string - try { - pageContent = await fs.promises.readFile(pageFilePath, { - encoding: 'utf8', - }) - } catch (err) { - return undefined - } + const pageContent = await fs.promises.readFile(pageFilePath, { + encoding: 'utf8', + }) // When gSSP or gSP is used, this page requires an execution runtime. If the // page config is not present, we fallback to the global runtime. Related @@ -217,6 +212,7 @@ export async function createEntrypoints( const hasRuntimeConfig = Object.keys(config.publicRuntimeConfig).length > 0 || Object.keys(config.serverRuntimeConfig).length > 0 + const hasReactRoot = !!config.experimental.reactRoot const defaultServerlessOptions = { absoluteAppPath: pages['/_app'], @@ -242,7 +238,7 @@ export async function createEntrypoints( 'base64' ), i18n: config.i18n ? JSON.stringify(config.i18n) : '', - reactRoot: config.experimental.reactRoot ? 'true' : '', + reactRoot: hasReactRoot ? 'true' : '', } const globalRuntime = config.experimental.runtime @@ -260,11 +256,14 @@ export async function createEntrypoints( const isReserved = isReservedPage(page) const isCustomError = isCustomErrorPage(page) const isFlight = isFlightPage(config, absolutePagePath) - const isEdgeRuntime = - (await getPageRuntime( - join(pagesDir, absolutePagePath.slice(PAGES_DIR_ALIAS.length + 1)), - globalRuntime - )) === 'edge' + const isInternalPages = !absolutePagePath.includes(PAGES_DIR_ALIAS) + const pageFilePath = isInternalPages + ? require.resolve(absolutePagePath) + : join(pagesDir, absolutePagePath.replace(PAGES_DIR_ALIAS, '')) + const pageRuntime = hasReactRoot + ? await getPageRuntime(pageFilePath, globalRuntime) + : undefined + const isEdgeRuntime = pageRuntime === 'edge' if (page.match(MIDDLEWARE_ROUTE)) { const loaderOpts: MiddlewareLoaderOptions = { diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index ed5d7ee5aea3..412d8bf3d50a 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -507,10 +507,9 @@ export default class HotReloader { this.hasServerComponents && isFlightPage(this.config, absolutePagePath) - const pageRuntimeConfig = await getPageRuntime( - absolutePagePath, - this.runtime - ) + const pageRuntimeConfig = this.hasReactRoot + ? await getPageRuntime(absolutePagePath, this.runtime) + : undefined const isEdgeSSRPage = pageRuntimeConfig === 'edge' && !isApiRoute if (isNodeServerCompilation && isEdgeSSRPage && !isCustomError) { diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 9f0fd64aba56..f691ae2a5a3f 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -283,10 +283,12 @@ export default class DevServer extends Server { pageName = pageName.replace(/\/index$/, '') || '/' invalidatePageRuntimeCache(fileName, safeTime) - const pageRuntimeConfig = await getPageRuntime( - fileName, - this.nextConfig.experimental.runtime - ) + const pageRuntimeConfig = this.renderOpts.reactRoot + ? await getPageRuntime( + fileName, + this.nextConfig.experimental.runtime + ) + : undefined const isEdgeRuntime = pageRuntimeConfig === 'edge' if ( diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index edb06c39c7e6..53a67a160cc3 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -43,6 +43,7 @@ export default function onDemandEntryHandler( ) { const { compilers } = multiCompiler const invalidator = new Invalidator(watcher, multiCompiler) + const hasReactRoot = !!nextConfig.experimental.reactRoot let lastClientAccessPages = [''] let doneCallbacks: EventEmitter | null = new EventEmitter() @@ -205,10 +206,12 @@ export default function onDemandEntryHandler( const isMiddleware = normalizedPage.match(MIDDLEWARE_ROUTE) const isApiRoute = normalizedPage.match(API_ROUTE) && !isMiddleware - const pageRuntimeConfig = await getPageRuntime( - absolutePagePath, - nextConfig.experimental.runtime - ) + const pageRuntimeConfig = hasReactRoot + ? await getPageRuntime( + absolutePagePath, + nextConfig.experimental.runtime + ) + : undefined const isEdgeServer = pageRuntimeConfig === 'edge' const isCustomError = isCustomErrorPage(page) From d380c06e63673bee0afcd3bf93cb809197663b01 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 30 Mar 2022 00:43:23 +0200 Subject: [PATCH 2/5] merge args --- packages/next/build/entries.ts | 33 ++++++++++++------- packages/next/build/index.ts | 10 ++---- packages/next/server/dev/hot-reloader.ts | 7 ++-- packages/next/server/dev/next-dev-server.ts | 10 +++--- .../server/dev/on-demand-entry-handler.ts | 10 +++--- 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index fe418f9bc861..db3f6292df69 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -1,4 +1,8 @@ -import type { PageRuntime, NextConfigComplete } from '../server/config-shared' +import type { + PageRuntime, + NextConfigComplete, + NextConfig, +} from '../server/config-shared' import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import fs from 'fs' import chalk from 'next/dist/compiled/chalk' @@ -105,16 +109,25 @@ const cachedPageRuntimeConfig = new Map() // could be thousands of pages existing. export async function getPageRuntime( pageFilePath: string, - globalRuntimeFallback?: 'nodejs' | 'edge' + nextConfig: NextConfig ): Promise { + if (!nextConfig.experimental?.reactRoot) return undefined + + const globalRuntime = nextConfig.experimental?.runtime const cached = cachedPageRuntimeConfig.get(pageFilePath) if (cached) { return cached[1] } - const pageContent = await fs.promises.readFile(pageFilePath, { - encoding: 'utf8', - }) + let pageContent: string + try { + pageContent = await fs.promises.readFile(pageFilePath, { + encoding: 'utf8', + }) + } catch (err) { + if (process.env.NODE_ENV === 'production') throw err + return undefined + } // When gSSP or gSP is used, this page requires an execution runtime. If the // page config is not present, we fallback to the global runtime. Related @@ -178,7 +191,7 @@ export async function getPageRuntime( if (!pageRuntime) { if (isRuntimeRequired) { - pageRuntime = globalRuntimeFallback + pageRuntime = globalRuntime } } @@ -241,8 +254,6 @@ export async function createEntrypoints( reactRoot: hasReactRoot ? 'true' : '', } - const globalRuntime = config.experimental.runtime - await Promise.all( Object.keys(pages).map(async (page) => { const absolutePagePath = pages[page] @@ -256,13 +267,11 @@ export async function createEntrypoints( const isReserved = isReservedPage(page) const isCustomError = isCustomErrorPage(page) const isFlight = isFlightPage(config, absolutePagePath) - const isInternalPages = !absolutePagePath.includes(PAGES_DIR_ALIAS) + const isInternalPages = !absolutePagePath.startsWith(PAGES_DIR_ALIAS) const pageFilePath = isInternalPages ? require.resolve(absolutePagePath) : join(pagesDir, absolutePagePath.replace(PAGES_DIR_ALIAS, '')) - const pageRuntime = hasReactRoot - ? await getPageRuntime(pageFilePath, globalRuntime) - : undefined + const pageRuntime = await getPageRuntime(pageFilePath, config) const isEdgeRuntime = pageRuntime === 'edge' if (page.match(MIDDLEWARE_ROUTE)) { diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index db60484e0745..ee7666d33949 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -971,13 +971,9 @@ export default async function build( p.startsWith(actualPage + '.') || p.startsWith(actualPage + '/index.') ) - const pageRuntime = - hasConcurrentFeatures && pagePath - ? await getPageRuntime( - join(pagesDir, pagePath), - config.experimental.runtime - ) - : undefined + const pageRuntime = pagePath + ? await getPageRuntime(join(pagesDir, pagePath), config) + : undefined if ( !isMiddlewareRoute && diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 412d8bf3d50a..e4240446806f 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -507,9 +507,10 @@ export default class HotReloader { this.hasServerComponents && isFlightPage(this.config, absolutePagePath) - const pageRuntimeConfig = this.hasReactRoot - ? await getPageRuntime(absolutePagePath, this.runtime) - : undefined + const pageRuntimeConfig = await getPageRuntime( + absolutePagePath, + this.config + ) const isEdgeSSRPage = pageRuntimeConfig === 'edge' && !isApiRoute if (isNodeServerCompilation && isEdgeSSRPage && !isCustomError) { diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index f691ae2a5a3f..1a8534b0d464 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -283,12 +283,10 @@ export default class DevServer extends Server { pageName = pageName.replace(/\/index$/, '') || '/' invalidatePageRuntimeCache(fileName, safeTime) - const pageRuntimeConfig = this.renderOpts.reactRoot - ? await getPageRuntime( - fileName, - this.nextConfig.experimental.runtime - ) - : undefined + const pageRuntimeConfig = await getPageRuntime( + fileName, + this.nextConfig + ) const isEdgeRuntime = pageRuntimeConfig === 'edge' if ( diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index 53a67a160cc3..9622f73e114b 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -206,12 +206,10 @@ export default function onDemandEntryHandler( const isMiddleware = normalizedPage.match(MIDDLEWARE_ROUTE) const isApiRoute = normalizedPage.match(API_ROUTE) && !isMiddleware - const pageRuntimeConfig = hasReactRoot - ? await getPageRuntime( - absolutePagePath, - nextConfig.experimental.runtime - ) - : undefined + const pageRuntimeConfig = await getPageRuntime( + absolutePagePath, + nextConfig + ) const isEdgeServer = pageRuntimeConfig === 'edge' const isCustomError = isCustomErrorPage(page) From 8de481dc995372b7745ca1bdf4fb5a662f9e15b9 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 30 Mar 2022 00:51:04 +0200 Subject: [PATCH 3/5] update tests --- test/unit/parse-page-runtime.test.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/unit/parse-page-runtime.test.ts b/test/unit/parse-page-runtime.test.ts index 11b4946ec1a2..5e34fe8737a1 100644 --- a/test/unit/parse-page-runtime.test.ts +++ b/test/unit/parse-page-runtime.test.ts @@ -3,24 +3,33 @@ import { join } from 'path' const fixtureDir = join(__dirname, 'fixtures') +function createNextConfig(runtime?: string) { + return { + experimental: { reactRoot: true, runtime }, + } +} + describe('parse page runtime config', () => { it('should parse nodejs runtime correctly', async () => { const runtime = await getPageRuntime( - join(fixtureDir, 'page-runtime/nodejs.js') + join(fixtureDir, 'page-runtime/nodejs.js'), + createNextConfig() ) expect(runtime).toBe('nodejs') }) it('should parse edge runtime correctly', async () => { const runtime = await getPageRuntime( - join(fixtureDir, 'page-runtime/edge.js') + join(fixtureDir, 'page-runtime/edge.js'), + createNextConfig() ) expect(runtime).toBe('edge') }) it('should return undefined if no runtime is specified', async () => { const runtime = await getPageRuntime( - join(fixtureDir, 'page-runtime/static.js') + join(fixtureDir, 'page-runtime/static.js'), + createNextConfig() ) expect(runtime).toBe(undefined) }) @@ -30,7 +39,7 @@ describe('fallback to the global runtime configuration', () => { it('should fallback when gSP is defined and exported', async () => { const runtime = await getPageRuntime( join(fixtureDir, 'page-runtime/fallback-with-gsp.js'), - 'edge' + createNextConfig('edge') ) expect(runtime).toBe('edge') }) @@ -38,7 +47,7 @@ describe('fallback to the global runtime configuration', () => { it('should fallback when gSP is re-exported from other module', async () => { const runtime = await getPageRuntime( join(fixtureDir, 'page-runtime/fallback-re-export-gsp.js'), - 'edge' + createNextConfig('edge') ) expect(runtime).toBe('edge') }) From 7733f8a66697133829790a215a2f610bc9e78b39 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 30 Mar 2022 10:40:38 +0200 Subject: [PATCH 4/5] type: partial --- packages/next/build/entries.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index db3f6292df69..cde116766a2f 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -109,7 +109,7 @@ const cachedPageRuntimeConfig = new Map() // could be thousands of pages existing. export async function getPageRuntime( pageFilePath: string, - nextConfig: NextConfig + nextConfig: Partial ): Promise { if (!nextConfig.experimental?.reactRoot) return undefined From 18893bae612f1b95a74b9a78f2672b797a003a8f Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 30 Mar 2022 20:26:30 +0200 Subject: [PATCH 5/5] fix lint --- packages/next/server/dev/on-demand-entry-handler.ts | 1 - test/unit/parse-page-runtime.test.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index 9622f73e114b..6df016d7e59e 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -43,7 +43,6 @@ export default function onDemandEntryHandler( ) { const { compilers } = multiCompiler const invalidator = new Invalidator(watcher, multiCompiler) - const hasReactRoot = !!nextConfig.experimental.reactRoot let lastClientAccessPages = [''] let doneCallbacks: EventEmitter | null = new EventEmitter() diff --git a/test/unit/parse-page-runtime.test.ts b/test/unit/parse-page-runtime.test.ts index 5e34fe8737a1..f37490d6b0c2 100644 --- a/test/unit/parse-page-runtime.test.ts +++ b/test/unit/parse-page-runtime.test.ts @@ -1,9 +1,10 @@ import { getPageRuntime } from 'next/dist/build/entries' +import type { PageRuntime } from 'next/dist/server/config-shared' import { join } from 'path' const fixtureDir = join(__dirname, 'fixtures') -function createNextConfig(runtime?: string) { +function createNextConfig(runtime?: PageRuntime) { return { experimental: { reactRoot: true, runtime }, }