diff --git a/packages/next/src/build/build-context.ts b/packages/next/src/build/build-context.ts new file mode 100644 index 00000000000000..0130be1746b166 --- /dev/null +++ b/packages/next/src/build/build-context.ts @@ -0,0 +1,50 @@ +import { LoadedEnvFiles } from '@next/env' +import { Ora } from 'next/dist/compiled/ora' +import { Rewrite } from '../lib/load-custom-routes' +import { __ApiPreviewProps } from '../server/api-utils' +import { NextConfigComplete } from '../server/config-shared' +import { Span } from '../trace' +import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin' + +// a global object to store context for the current build +// this is used to pass data between different steps of the build without having +// to pass it through function arguments. +// Not exhaustive, but should be extended to as needed whilst refactoring +export const NextBuildContext: Partial<{ + // core fields + dir: string + buildId: string + config: NextConfigComplete + appDir: string + pagesDir: string + rewrites: { + fallback: Rewrite[] + afterFiles: Rewrite[] + beforeFiles: Rewrite[] + } + loadedEnvFiles: LoadedEnvFiles + previewProps: __ApiPreviewProps + mappedPages: + | { + [page: string]: string + } + | undefined + mappedAppPages: + | { + [page: string]: string + } + | undefined + mappedRootPaths: { + [page: string]: string + } + + // misc fields + telemetryPlugin: TelemetryPlugin + buildSpinner: Ora + nextBuildSpan: Span + + // cli fields + reactProductionProfiling: boolean + noMangling: boolean + appDirOnly: boolean +}> = {} diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 50a37c4b832822..87043e889532c1 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -85,7 +85,7 @@ import { generateBuildId } from './generate-build-id' import { isWriteable } from './is-writeable' import * as Log from './output/log' import createSpinner from './spinner' -import { trace, flushAllTraces, setGlobal, Span } from '../trace' +import { trace, flushAllTraces, setGlobal } from '../trace' import { detectConflictingPaths, computeFromManifest, @@ -103,7 +103,6 @@ import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import { NextConfigComplete } from '../server/config-shared' import isError, { NextError } from '../lib/is-error' import { isEdgeRuntime } from '../lib/is-edge-runtime' -import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin' import { MiddlewareManifest } from './webpack/plugins/middleware-plugin' import { recursiveCopy } from '../lib/recursive-copy' import { recursiveReadDir } from '../lib/recursive-readdir' @@ -121,6 +120,7 @@ import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin' import { RSC, RSC_VARY_HEADER } from '../client/components/app-router-headers' import { webpackBuild } from './webpack-build' +import { NextBuildContext } from './build-context' export type SsgRoute = { initialRevalidateSeconds: number | false @@ -143,13 +143,6 @@ export type PrerenderManifest = { preview: __ApiPreviewProps } -export const NextBuildContext: Partial<{ - telemetryPlugin: TelemetryPlugin - buildSpinner: any - nextBuildSpan: Span - dir: string -}> = {} - /** * typescript will be loaded in "next/lib/verifyTypeScriptSetup" and * then passed to "next/lib/typescript/runTypeCheck" as a parameter. @@ -251,33 +244,40 @@ export default async function build( const nextBuildSpan = trace('next-build', undefined, { version: process.env.__NEXT_VERSION as string, }) + NextBuildContext.nextBuildSpan = nextBuildSpan NextBuildContext.dir = dir + NextBuildContext.appDirOnly = appDirOnly + NextBuildContext.reactProductionProfiling = reactProductionProfiling + NextBuildContext.noMangling = noMangling const buildResult = await nextBuildSpan.traceAsyncFn(async () => { // attempt to load global env values so they are available in next.config.js const { loadedEnvFiles } = nextBuildSpan .traceChild('load-dotenv') .traceFn(() => loadEnvConfig(dir, false, Log)) + NextBuildContext.loadedEnvFiles = loadedEnvFiles const config: NextConfigComplete = await nextBuildSpan .traceChild('load-next-config') .traceAsyncFn(() => loadConfig(PHASE_PRODUCTION_BUILD, dir)) + NextBuildContext.config = config const distDir = path.join(dir, config.distDir) setGlobal('phase', PHASE_PRODUCTION_BUILD) setGlobal('distDir', distDir) - const { target } = config const buildId: string = await nextBuildSpan .traceChild('generate-buildid') .traceAsyncFn(() => generateBuildId(config.generateBuildId, nanoid)) + NextBuildContext.buildId = buildId const customRoutes: CustomRoutes = await nextBuildSpan .traceChild('load-custom-routes') .traceAsyncFn(() => loadCustomRoutes(config)) const { headers, rewrites, redirects } = customRoutes + NextBuildContext.rewrites = rewrites const cacheDir = path.join(distDir, 'cache') if (ciEnvironment.isCI && !ciEnvironment.hasNextSupport) { @@ -324,6 +324,9 @@ export default async function build( }) const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled) + NextBuildContext.pagesDir = pagesDir + NextBuildContext.appDir = appDir + const isSrcDir = path .relative(dir, pagesDir || appDir || '') .startsWith('src') @@ -516,6 +519,7 @@ export default async function build( previewModeSigningKey: crypto.randomBytes(32).toString('hex'), previewModeEncryptionKey: crypto.randomBytes(32).toString('hex'), } + NextBuildContext.previewProps = previewProps const mappedPages = nextBuildSpan .traceChild('create-pages-mapping') @@ -528,6 +532,7 @@ export default async function build( pagesDir, }) ) + NextBuildContext.mappedPages = mappedPages let mappedAppPages: { [page: string]: string } | undefined let denormalizedAppPages: string[] | undefined @@ -544,6 +549,7 @@ export default async function build( pagesDir: pagesDir, }) ) + NextBuildContext.mappedAppPages = mappedAppPages } let mappedRootPaths: { [page: string]: string } = {} @@ -556,6 +562,7 @@ export default async function build( pagesDir: pagesDir, }) } + NextBuildContext.mappedRootPaths = mappedRootPaths const pagesPageKeys = Object.keys(mappedPages) @@ -919,32 +926,7 @@ export default async function build( ignore: [] as string[], })) - const webpackBuildDuration = await webpackBuild( - { - buildId, - config, - pagesDir, - reactProductionProfiling, - rewrites, - target, - appDir, - noMangling, - }, - { - buildId, - config, - envFiles: loadedEnvFiles, - isDev: false, - pages: mappedPages, - pagesDir, - previewMode: previewProps, - rootDir: dir, - rootPaths: mappedRootPaths, - appDir, - appPaths: mappedAppPages, - pageExtensions: config.pageExtensions, - } - ) + const webpackBuildDuration = await webpackBuild() telemetry.record( eventBuildCompleted(pagesPaths, { diff --git a/packages/next/src/build/webpack-build.ts b/packages/next/src/build/webpack-build.ts index 8be8da5a03cba4..96c5689f41c7c2 100644 --- a/packages/next/src/build/webpack-build.ts +++ b/packages/next/src/build/webpack-build.ts @@ -13,11 +13,9 @@ import getBaseWebpackConfig from './webpack-config' import { NextError } from '../lib/is-error' import { injectedClientEntries } from './webpack/plugins/flight-client-entry-plugin' import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin' -import { Rewrite } from '../lib/load-custom-routes' -import { NextConfigComplete } from '../server/config-shared' -import { MiddlewareMatcher } from './analysis/get-page-static-info' -import { NextBuildContext } from '.' -import { CreateEntrypointsParams, createEntrypoints } from './entries' +import { NextBuildContext } from './build-context' + +import { createEntrypoints } from './entries' type CompilerResult = { errors: webpack.StatsError[] @@ -35,23 +33,7 @@ function isTelemetryPlugin(plugin: unknown): plugin is TelemetryPlugin { return plugin instanceof TelemetryPlugin } -export async function webpackBuild( - commonWebpackOptions: { - buildId: string - config: NextConfigComplete - pagesDir: string | undefined - reactProductionProfiling: boolean - rewrites: { - fallback: Rewrite[] - afterFiles: Rewrite[] - beforeFiles: Rewrite[] - } - target: string - appDir: string | undefined - noMangling: boolean - }, - entrypointsParams: CreateEntrypointsParams -): Promise { +export async function webpackBuild(): Promise { let result: CompilerResult | null = { warnings: [], errors: [], @@ -61,13 +43,41 @@ export async function webpackBuild( const nextBuildSpan = NextBuildContext.nextBuildSpan! const buildSpinner = NextBuildContext.buildSpinner const dir = NextBuildContext.dir! + await (async () => { // IIFE to isolate locals and avoid retaining memory too long const runWebpackSpan = nextBuildSpan.traceChild('run-webpack-compiler') const entrypoints = await nextBuildSpan .traceChild('create-entrypoints') - .traceAsyncFn(() => createEntrypoints(entrypointsParams)) + .traceAsyncFn(() => + createEntrypoints({ + buildId: NextBuildContext.buildId!, + config: NextBuildContext.config!, + envFiles: NextBuildContext.loadedEnvFiles!, + isDev: false, + rootDir: dir, + pageExtensions: NextBuildContext.config!.pageExtensions!, + pagesDir: NextBuildContext.pagesDir!, + appDir: NextBuildContext.appDir!, + pages: NextBuildContext.mappedPages!, + appPaths: NextBuildContext.mappedAppPages!, + previewMode: NextBuildContext.previewProps!, + rootPaths: NextBuildContext.mappedRootPaths!, + }) + ) + + const commonWebpackOptions = { + isServer: false, + buildId: NextBuildContext.buildId!, + config: NextBuildContext.config!, + target: NextBuildContext.config!.target!, + appDir: NextBuildContext.appDir!, + pagesDir: NextBuildContext.pagesDir!, + rewrites: NextBuildContext.rewrites!, + reactProductionProfiling: NextBuildContext.reactProductionProfiling!, + noMangling: NextBuildContext.noMangling!, + } const configs = await runWebpackSpan .traceChild('generate-webpack-config') @@ -75,8 +85,8 @@ export async function webpackBuild( Promise.all([ getBaseWebpackConfig(dir, { ...commonWebpackOptions, - runWebpackSpan, middlewareMatchers: entrypoints.middlewareMatchers, + runWebpackSpan, compilerType: COMPILER_NAMES.client, entrypoints: entrypoints.client, }),