diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index c6a57df897a0..85e39d25cfc0 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -1,3 +1,5 @@ +import type { PageRuntime, NextConfigComplete } from '../server/config-shared' +import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import fs from 'fs' import chalk from 'next/dist/compiled/chalk' import { posix, join } from 'path' @@ -12,11 +14,9 @@ import { MiddlewareLoaderOptions } from './webpack/loaders/next-middleware-loade import { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-loader' import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader' import { LoadedEnvFiles } from '@next/env' -import { NextConfigComplete } from '../server/config-shared' import { parse } from '../build/swc' import { isCustomErrorPage, isFlightPage, isReservedPage } from './utils' import { ssrEntries } from './webpack/plugins/middleware-plugin' -import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import { MIDDLEWARE_RUNTIME_WEBPACK, MIDDLEWARE_SSR_RUNTIME_WEBPACK, @@ -99,17 +99,14 @@ type Entrypoints = { edgeServer: webpack5.EntryObject } -const cachedPageRuntimeConfig = new Map< - string, - [number, 'nodejs' | 'edge' | undefined] ->() +const cachedPageRuntimeConfig = new Map() // @TODO: We should limit the maximum concurrency of this function as there // could be thousands of pages existing. export async function getPageRuntime( pageFilePath: string, globalRuntimeFallback?: 'nodejs' | 'edge' -): Promise<'nodejs' | 'edge' | undefined> { +): Promise { const cached = cachedPageRuntimeConfig.get(pageFilePath) if (cached) { return cached[1] @@ -129,7 +126,7 @@ export async function getPageRuntime( // discussion: // https://github.com/vercel/next.js/discussions/34179 let isRuntimeRequired: boolean = false - let pageRuntime: 'nodejs' | 'edge' | undefined = undefined + let pageRuntime: PageRuntime = undefined // Since these configurations should always be static analyzable, we can // skip these cases that "runtime" and "gSP", "gSSP" are not included in the diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 8709592226b0..b14888080a37 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -969,7 +969,7 @@ export default async function build( join(pagesDir, pagePath), config.experimental.runtime ) - : null + : undefined if ( !isMiddlewareRoute && @@ -1084,14 +1084,13 @@ export default async function build( totalSize: allSize, static: isStatic, isSsg, - isWebSsr: - hasConcurrentFeatures && - !isMiddlewareRoute && - !isReservedPage(page) && - !isCustomErrorPage(page), isHybridAmp, ssgPageRoutes, initialRevalidateSeconds: false, + runtime: + !isReservedPage(page) && !isCustomErrorPage(page) + ? pageRuntime + : undefined, pageDuration: undefined, ssgPageDurations: undefined, }) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index a2bf0c53337b..110ef701c36c 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1,3 +1,5 @@ +import type { NextConfigComplete, PageRuntime } from '../server/config-shared' + import '../server/node-polyfill-fetch' import chalk from 'next/dist/compiled/chalk' import getGzipSize from 'next/dist/compiled/gzip-size' @@ -37,7 +39,6 @@ import * as Log from './output/log' import { loadComponents } from '../server/load-components' import { trace } from '../trace' import { setHttpAgentOptions } from '../server/config' -import { NextConfigComplete } from '../server/config-shared' import isError from '../lib/is-error' import { recursiveDelete } from '../lib/recursive-delete' import { Sema } from 'next/dist/compiled/async-sema' @@ -77,11 +78,11 @@ export interface PageInfo { totalSize: number static: boolean isSsg: boolean - isWebSsr: boolean ssgPageRoutes: string[] | null initialRevalidateSeconds: number | false pageDuration: number | undefined ssgPageDurations: number[] | undefined + runtime: PageRuntime } export async function printTreeView( @@ -195,12 +196,12 @@ export async function printTreeView( ? ' ' : item.endsWith('/_middleware') ? 'ƒ' - : pageInfo?.isWebSsr - ? 'ℇ' : pageInfo?.static ? '○' : pageInfo?.isSsg ? '●' + : pageInfo?.runtime === 'edge' + ? 'ℇ' : 'λ' usedSymbols.add(symbol) diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 0007760e4017..029d39957155 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -7,6 +7,8 @@ import { imageConfigDefault, } from '../shared/lib/image-config' +export type PageRuntime = 'nodejs' | 'edge' | undefined + export type NextConfigComplete = Required & { images: Required typescript: Required @@ -101,7 +103,7 @@ export interface ExperimentalConfig { craCompat?: boolean esmExternals?: boolean | 'loose' isrMemoryCacheSize?: number - runtime?: 'nodejs' | 'edge' + runtime?: Exclude serverComponents?: boolean fullySpecified?: boolean urlImports?: NonNullable['buildHttp'] diff --git a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js index 62ebdcadf426..d5cabd5f9a4e 100644 --- a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js +++ b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js @@ -1,34 +1,16 @@ /* eslint-env jest */ import { join } from 'path' -import { - // File, - nextBuild as _nextBuild, - nextStart as _nextStart, -} from 'next-test-utils' - import { findPort, killApp, renderViaHTTP } from 'next-test-utils' - -const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')] +import { nextBuild, nextStart } from './utils' const appDir = join(__dirname, '../switchable-runtime') -// const nextConfig = new File(join(appDir, 'next.config.js')) - -async function nextBuild(dir, options) { - return await _nextBuild(dir, [], { - ...options, - stdout: true, - stderr: true, - nodeArgs, - }) -} -async function nextStart(dir, port) { - return await _nextStart(dir, port, { - stdout: true, - stderr: true, - nodeArgs, - }) +function splitLines(text) { + return text + .split(/\r?\n/g) + .map((str) => str.trim()) + .filter(Boolean) } async function testRoute(appPort, url, { isStatic, isEdge }) { @@ -54,7 +36,8 @@ describe('Without global runtime configuration', () => { beforeAll(async () => { context.appPort = await findPort() - const { stderr } = await nextBuild(context.appDir) + const { stdout, stderr } = await nextBuild(context.appDir) + context.stdout = stdout context.stderr = stderr context.server = await nextStart(context.appDir, context.appPort) }) @@ -124,4 +107,26 @@ describe('Without global runtime configuration', () => { isEdge: true, }) }) + + it('should display correct tree view with page types in terminal', async () => { + const stdoutLines = splitLines(context.stdout).filter((line) => + /^[┌├└/]/.test(line) + ) + const expectedOutputLines = splitLines(` + ┌ λ /404 + ├ ℇ /edge + ├ ℇ /edge-rsc + ├ ○ /node + ├ ○ /node-rsc + ├ ● /node-rsc-ssg + ├ λ /node-rsc-ssr + ├ ● /node-ssg + ├ λ /node-ssr + └ ○ /static + `) + const isMatched = expectedOutputLines.every((line, index) => + stdoutLines[index].startsWith(line) + ) + expect(isMatched).toBe(true) + }) })