diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 63b70fc1fec11..465c0093c4191 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -280,30 +280,10 @@ export default async function build( const publicDir = path.join(dir, 'public') const isAppDirEnabled = !!config.experimental.appDir - const initialRequireHookFilePath = require.resolve( - 'next/dist/server/initialize-require-hook' - ) - const content = await promises.readFile( - initialRequireHookFilePath, - 'utf8' - ) if (isAppDirEnabled) { process.env.NEXT_PREBUNDLED_REACT = '1' } - await promises - .writeFile( - initialRequireHookFilePath, - content.replace( - /isPrebundled = (true|false)/, - `isPrebundled = ${isAppDirEnabled}` - ) - ) - .catch((err) => { - if (isAppDirEnabled) { - throw err - } - }) const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled) NextBuildContext.pagesDir = pagesDir @@ -1067,10 +1047,9 @@ export default async function build( const timeout = config.staticPageGenerationTimeout || 0 const sharedPool = config.experimental.sharedPool || false - const staticWorker = sharedPool + const staticWorkerPath = sharedPool ? require.resolve('./worker') : require.resolve('./utils') - let infoPrinted = false let appPathsManifest: Record = {} const appPathRoutes: Record = {} @@ -1111,69 +1090,89 @@ export default async function build( 4 ) - const staticWorkers = new Worker(staticWorker, { - timeout: timeout * 1000, - onRestart: (method, [arg], attempts) => { - if (method === 'exportPage') { - const { path: pagePath } = arg - if (attempts >= 3) { - throw new Error( - `Static page generation for ${pagePath} is still timing out after 3 attempts. See more info here https://nextjs.org/docs/messages/static-page-generation-timeout` + function createStaticWorker(type: 'app' | 'pages') { + const numWorkersPerType = isAppDirEnabled + ? Math.max(1, ~~(numWorkers / 2)) + : numWorkers + + let infoPrinted = false + + return new Worker(staticWorkerPath, { + timeout: timeout * 1000, + onRestart: (method, [arg], attempts) => { + if (method === 'exportPage') { + const { path: pagePath } = arg + if (attempts >= 3) { + throw new Error( + `Static page generation for ${pagePath} is still timing out after 3 attempts. See more info here https://nextjs.org/docs/messages/static-page-generation-timeout` + ) + } + Log.warn( + `Restarted static page generation for ${pagePath} because it took more than ${timeout} seconds` + ) + } else { + const pagePath = arg + if (attempts >= 2) { + throw new Error( + `Collecting page data for ${pagePath} is still timing out after 2 attempts. See more info here https://nextjs.org/docs/messages/page-data-collection-timeout` + ) + } + Log.warn( + `Restarted collecting page data for ${pagePath} because it took more than ${timeout} seconds` ) } - Log.warn( - `Restarted static page generation for ${pagePath} because it took more than ${timeout} seconds` - ) - } else { - const pagePath = arg - if (attempts >= 2) { - throw new Error( - `Collecting page data for ${pagePath} is still timing out after 2 attempts. See more info here https://nextjs.org/docs/messages/page-data-collection-timeout` + if (!infoPrinted) { + Log.warn( + 'See more info here https://nextjs.org/docs/messages/static-page-generation-timeout' ) + infoPrinted = true } - Log.warn( - `Restarted collecting page data for ${pagePath} because it took more than ${timeout} seconds` - ) - } - if (!infoPrinted) { - Log.warn( - 'See more info here https://nextjs.org/docs/messages/static-page-generation-timeout' - ) - infoPrinted = true - } - }, - numWorkers, - enableWorkerThreads: config.experimental.workerThreads, - computeWorkerKey(method, ...args) { - if (method === 'exportPage') { - const typedArgs = args as Parameters< - typeof import('./worker').exportPage - > - return typedArgs[0].pathMap.page - } else if (method === 'isPageStatic') { - const typedArgs = args as Parameters< - typeof import('./worker').isPageStatic - > - return typedArgs[0].originalAppPath || typedArgs[0].page - } - return method - }, - exposedMethods: sharedPool - ? [ - 'hasCustomGetInitialProps', - 'isPageStatic', - 'getNamedExports', - 'exportPage', - ] - : ['hasCustomGetInitialProps', 'isPageStatic', 'getNamedExports'], - }) as Worker & - Pick< - typeof import('./worker'), - | 'hasCustomGetInitialProps' - | 'isPageStatic' - | 'getNamedExports' - | 'exportPage' - > + }, + numWorkers: numWorkersPerType, + forkOptions: { + env: { + ...process.env, + NEXT_PREBUNDLED_REACT_WORKER: type === 'app' ? '1' : '', + __NEXT_PRIVATE_PREBUNDLED_REACT: type === 'app' ? '1' : '', + }, + }, + enableWorkerThreads: config.experimental.workerThreads, + computeWorkerKey(method, ...args) { + if (method === 'exportPage') { + const typedArgs = args as Parameters< + typeof import('./worker').exportPage + > + return typedArgs[0].pathMap.page + } else if (method === 'isPageStatic') { + const typedArgs = args as Parameters< + typeof import('./worker').isPageStatic + > + return typedArgs[0].originalAppPath || typedArgs[0].page + } + return method + }, + exposedMethods: sharedPool + ? [ + 'hasCustomGetInitialProps', + 'isPageStatic', + 'getNamedExports', + 'exportPage', + ] + : ['hasCustomGetInitialProps', 'isPageStatic', 'getNamedExports'], + }) as Worker & + Pick< + typeof import('./worker'), + | 'hasCustomGetInitialProps' + | 'isPageStatic' + | 'getNamedExports' + | 'exportPage' + > + } + + const pagesStaticWorkers = createStaticWorker('pages') + const appStaticWorkers = isAppDirEnabled + ? createStaticWorker('app') + : undefined const analysisBegin = process.hrtime() const staticCheckSpan = nextBuildSpan.traceChild('static-check') @@ -1195,7 +1194,7 @@ export default async function build( nonStaticErrorPageSpan.traceAsyncFn( async () => hasCustomErrorPage && - (await staticWorkers.hasCustomGetInitialProps( + (await pagesStaticWorkers.hasCustomGetInitialProps( '/_error', distDir, runtimeEnvConfig, @@ -1206,7 +1205,7 @@ export default async function build( const errorPageStaticResult = nonStaticErrorPageSpan.traceAsyncFn( async () => hasCustomErrorPage && - staticWorkers.isPageStatic({ + pagesStaticWorkers.isPageStatic({ page: '/_error', distDir, configFileName, @@ -1222,14 +1221,14 @@ export default async function build( const appPageToCheck = '/_app' const customAppGetInitialPropsPromise = - staticWorkers.hasCustomGetInitialProps( + pagesStaticWorkers.hasCustomGetInitialProps( appPageToCheck, distDir, runtimeEnvConfig, true ) - const namedExportsPromise = staticWorkers.getNamedExports( + const namedExportsPromise = pagesStaticWorkers.getNamedExports( appPageToCheck, distDir, runtimeEnvConfig @@ -1385,7 +1384,11 @@ export default async function build( checkPageSpan.traceChild('is-page-static') let workerResult = await isPageStaticSpan.traceAsyncFn( () => { - return staticWorkers.isPageStatic({ + return ( + pageType === 'app' + ? appStaticWorkers + : pagesStaticWorkers + )!.isPageStatic({ page, originalAppPath, distDir, @@ -1621,7 +1624,11 @@ export default async function build( hasNonStaticErrorPage: nonStaticErrorPage, } - if (!sharedPool) staticWorkers.end() + if (!sharedPool) { + pagesStaticWorkers.end() + appStaticWorkers?.end() + } + return returnValue }) @@ -2290,12 +2297,16 @@ export default async function build( pages: combinedPages, outdir: path.join(distDir, 'export'), statusMessage: 'Generating static pages', + exportAppPageWorker: sharedPool + ? appStaticWorkers?.exportPage.bind(appStaticWorkers) + : undefined, exportPageWorker: sharedPool - ? staticWorkers.exportPage.bind(staticWorkers) + ? pagesStaticWorkers.exportPage.bind(pagesStaticWorkers) : undefined, endWorker: sharedPool ? async () => { - await staticWorkers.end() + await pagesStaticWorkers.end() + await appStaticWorkers?.end() } : undefined, } @@ -2723,7 +2734,8 @@ export default async function build( } // ensure the worker is not left hanging - staticWorkers.close() + pagesStaticWorkers.close() + appStaticWorkers?.close() const analysisEnd = process.hrtime(analysisBegin) telemetry.record( diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 89875c91e40f8..e82da233ed8e0 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -14,6 +14,7 @@ import type { MiddlewareManifest } from './webpack/plugins/middleware-plugin' import type { AppRouteUserlandModule } from '../server/future/route-modules/app-route/module' import type { StaticGenerationAsyncStorage } from '../client/components/static-generation-async-storage' +import '../server/require-hook' import '../server/node-polyfill-fetch' import chalk from 'next/dist/compiled/chalk' import getGzipSize from 'next/dist/compiled/gzip-size' @@ -52,21 +53,12 @@ import { Sema } from 'next/dist/compiled/async-sema' import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path' import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path' import { getRuntimeContext } from '../server/web/sandbox' -import { - loadRequireHook, - overrideBuiltInReactPackages, -} from './webpack/require-hook' import { isClientReference } from '../lib/client-reference' import { StaticGenerationAsyncStorageWrapper } from '../server/async-storage/static-generation-async-storage-wrapper' import { IncrementalCache } from '../server/lib/incremental-cache' import { patchFetch } from '../server/lib/patch-fetch' import { nodeFs } from '../server/lib/node-fs-methods' -loadRequireHook() -if (process.env.NEXT_PREBUNDLED_REACT) { - overrideBuiltInReactPackages() -} - // expose AsyncLocalStorage on global for react usage const { AsyncLocalStorage } = require('async_hooks') ;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 873799f39a3a8..5374167eb1331 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -974,8 +974,6 @@ export default async function getBaseWebpackConfig( ] } - const reactDir = path.dirname(require.resolve('react/package.json')) - const reactDomDir = path.dirname(require.resolve('react-dom/package.json')) let hasExternalOtelApiPackage = false try { require.resolve('@opentelemetry/api') @@ -1042,26 +1040,6 @@ export default async function getBaseWebpackConfig( next: NEXT_PROJECT_ROOT, - ...(hasServerComponents - ? { - // For react and react-dom, alias them dynamically for server layer - // and others in the loaders configuration - 'react-dom/client$': 'next/dist/compiled/react-dom/client', - 'react-dom/server$': 'next/dist/compiled/react-dom/server', - 'react-dom/server.browser$': - 'next/dist/compiled/react-dom/server.browser', - 'react/jsx-dev-runtime$': - 'next/dist/compiled/react/jsx-dev-runtime', - 'react/jsx-runtime$': 'next/dist/compiled/react/jsx-runtime', - } - : { - react: reactDir, - 'react-dom$': reactDomDir, - 'react-dom/server$': `${reactDomDir}/server`, - 'react-dom/server.browser$': `${reactDomDir}/server.browser`, - 'react-dom/client$': `${reactDomDir}/client`, - }), - 'styled-jsx/style$': require.resolve(`styled-jsx/style`), 'styled-jsx$': require.resolve(`styled-jsx`), @@ -1233,8 +1211,27 @@ export default async function getBaseWebpackConfig( return `commonjs next/dist/lib/import-next-warning` } + const isAppLayer = [ + WEBPACK_LAYERS.server, + WEBPACK_LAYERS.client, + WEBPACK_LAYERS.appClient, + WEBPACK_LAYERS.action, + ].includes(layer!) + + if ( + request === 'react/jsx-dev-runtime' || + request === 'react/jsx-runtime' + ) { + if (isAppLayer) { + return `commonjs next/dist/compiled/${request}` + } + return + } + // Special internal modules that must be bundled for Server Components. if (layer === WEBPACK_LAYERS.server) { + // React needs to be bundled for Server Components so the special + // `react-server` export condition can be used. if ( reactPackagesRegex.test(request) || request === 'next/dist/compiled/react-server-dom-webpack/server.edge' @@ -1256,11 +1253,13 @@ export default async function getBaseWebpackConfig( // override react-dom to server-rendering-stub for server if ( request === 'react-dom' && - (layer === WEBPACK_LAYERS.client || layer === WEBPACK_LAYERS.server) + (layer === WEBPACK_LAYERS.client || + layer === WEBPACK_LAYERS.server || + layer === WEBPACK_LAYERS.action) ) { request = 'react-dom/server-rendering-stub' } - return `commonjs ${hasAppDir ? 'next/dist/compiled/' : ''}${request}` + return `commonjs ${isAppLayer ? 'next/dist/compiled/' : ''}${request}` } const notExternalModules = @@ -1762,6 +1761,38 @@ export default async function getBaseWebpackConfig( }, module: { rules: [ + ...(hasAppDir + ? [ + { + test: codeCondition.test, + issuerLayer: { + or: [ + WEBPACK_LAYERS.server, + WEBPACK_LAYERS.client, + WEBPACK_LAYERS.appClient, + WEBPACK_LAYERS.action, + WEBPACK_LAYERS.shared, + ], + }, + resolve: { + alias: { + // Alias next/head component to noop for RSC + [require.resolve('next/head')]: require.resolve( + 'next/dist/client/components/noop-head' + ), + // Alias next/dynamic + [require.resolve('next/dynamic')]: require.resolve( + 'next/dist/shared/lib/app-dynamic' + ), + 'react/jsx-runtime$': + 'next/dist/compiled/react/jsx-runtime', + 'react/jsx-dev-runtime$': + 'next/dist/compiled/react/jsx-dev-runtime', + }, + }, + }, + ] + : []), ...(hasAppDir && !isClient ? [ { @@ -1829,29 +1860,6 @@ export default async function getBaseWebpackConfig( : []), ...(hasServerComponents ? [ - { - test: codeCondition.test, - issuerLayer: { - or: [ - WEBPACK_LAYERS.server, - WEBPACK_LAYERS.client, - WEBPACK_LAYERS.appClient, - WEBPACK_LAYERS.action, - ], - }, - resolve: { - alias: { - // Alias next/head component to noop for RSC - [require.resolve('next/head')]: require.resolve( - 'next/dist/client/components/noop-head' - ), - // Alias next/dynamic - [require.resolve('next/dynamic')]: require.resolve( - 'next/dist/shared/lib/app-dynamic' - ), - }, - }, - }, { // Alias react-dom for ReactDOM.preload usage. // Alias react for switching between default set and share subset. diff --git a/packages/next/src/build/webpack/require-hook.ts b/packages/next/src/build/webpack/require-hook.ts deleted file mode 100644 index 7d65d10b99cb6..0000000000000 --- a/packages/next/src/build/webpack/require-hook.ts +++ /dev/null @@ -1,78 +0,0 @@ -// sync injects a hook for webpack and webpack/... requires to use the internal ncc webpack version -// this is in order for userland plugins to attach to the same webpack instance as next.js -// the individual compiled modules are as defined for the compilation in bundles/webpack/packages/* - -const hookPropertyMap = new Map() - -let initialized = false -function setupResolve() { - if (initialized) { - return - } - initialized = true - const mod = require('module') - const resolveFilename = mod._resolveFilename - mod._resolveFilename = function ( - request: string, - parent: any, - isMain: boolean, - options: any - ) { - const hookResolved = hookPropertyMap.get(request) - if (hookResolved) request = hookResolved - return resolveFilename.call(mod, request, parent, isMain, options) - } -} - -export function setRequireOverrides(aliases: [string, string][]) { - for (const [key, value] of aliases) { - hookPropertyMap.set(key, value) - } -} - -export function loadRequireHook(aliases: [string, string][] = []) { - const defaultAliases = [ - ...aliases, - // Use `require.resolve` explicitly to make them statically analyzable - // styled-jsx needs to be resolved as the external dependency. - ['styled-jsx', require.resolve('styled-jsx')], - ['styled-jsx/style', require.resolve('styled-jsx/style')], - ['styled-jsx/style', require.resolve('styled-jsx/style')], - ['server-only', require.resolve('next/dist/compiled/server-only')], - ['client-only', require.resolve('next/dist/compiled/client-only')], - ] as [string, string][] - - setRequireOverrides(defaultAliases) - - setupResolve() -} - -export function overrideBuiltInReactPackages() { - setRequireOverrides([ - ['react', require.resolve('next/dist/compiled/react')], - [ - 'react/jsx-runtime', - require.resolve('next/dist/compiled/react/jsx-runtime'), - ], - [ - 'react/jsx-dev-runtime', - require.resolve('next/dist/compiled/react/jsx-dev-runtime'), - ], - [ - 'react-dom', - require.resolve('next/dist/compiled/react-dom/server-rendering-stub'), - ], - [ - 'react-dom/client', - require.resolve('next/dist/compiled/react-dom/client'), - ], - [ - 'react-dom/server', - require.resolve('next/dist/compiled/react-dom/server'), - ], - [ - 'react-dom/server.browser', - require.resolve('next/dist/compiled/react-dom/server.browser'), - ], - ]) -} diff --git a/packages/next/src/build/worker.ts b/packages/next/src/build/worker.ts index 76a13ebfd878b..0916ac40c4cc2 100644 --- a/packages/next/src/build/worker.ts +++ b/packages/next/src/build/worker.ts @@ -1,3 +1,5 @@ +import '../server/require-hook' + export * from './utils' import exportPage from '../export/worker' export { exportPage } diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js index 691360e3f9866..37d009216c677 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js @@ -14,7 +14,7 @@ if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; -var React = require('react'); +var React = require("next/dist/compiled/react"); var ReactDOM = require('react-dom'); var ReactVersion = '18.3.0-next-1f248bdd7-20230419'; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js index c19551fee52fd..b728c4d35efd6 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.min.js @@ -7,7 +7,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -'use strict';var aa=require("react"),ba=require("react-dom");function m(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cfa||(a.current=ea[fa],ea[fa]=null,fa--)}function E(a,b){fa++;ea[fa]=a.current;a.current=b}var ia=ha(null),ja=ha(null),ka=ha(null);function la(a,b){E(ka,b);E(ja,a);E(ia,null);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:ma(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=ma(b,a)}D(ia);E(ia,b)}function na(){D(ia);D(ja);D(ka)} function oa(a){var b=ia.current;var c=ma(b,a.type);b!==c&&(E(ja,a),E(ia,c))}function pa(a){ja.current===a&&(D(ia),D(ja))} var qa=ba.unstable_scheduleCallback,ra=ba.unstable_cancelCallback,sa=ba.unstable_shouldYield,ta=ba.unstable_requestPaint,ua=ba.unstable_now,va=ba.unstable_getCurrentPriorityLevel,wa=ba.unstable_ImmediatePriority,xa=ba.unstable_UserBlockingPriority,ya=ba.unstable_NormalPriority,za=ba.unstable_LowPriority,Aa=ba.unstable_IdlePriority,Ba=aa.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,Ca=null,Da=null; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom.profiling.min.js b/packages/next/src/compiled/react-dom/cjs/react-dom.profiling.min.js index 59f98f34e2296..68837352e61de 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom.profiling.min.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom.profiling.min.js @@ -21,7 +21,7 @@ if ( /* Modernizr 3.0.0pre (Custom Build) | MIT */ -'use strict';var aa=require("next/dist/compiled/scheduler"),ba=require("react"),ca={usingClientEntryPoint:!1,Events:null,Dispatcher:{current:null}};function r(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cfa||(a.current=ea[fa],ea[fa]=null,fa--)}function E(a,b){fa++;ea[fa]=a.current;a.current=b}var ia=ha(null),ja=ha(null),ka=ha(null);function la(a,b){E(ka,b);E(ja,a);E(ia,null);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:ma(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=ma(b,a)}C(ia);E(ia,b)}function na(){C(ia);C(ja);C(ka)} function oa(a){var b=ia.current;var c=ma(b,a.type);b!==c&&(E(ja,a),E(ia,c))}function pa(a){ja.current===a&&(C(ia),C(ja))} var qa=aa.unstable_scheduleCallback,ra=aa.unstable_cancelCallback,sa=aa.unstable_shouldYield,ta=aa.unstable_requestPaint,ua=aa.unstable_now,va=aa.unstable_getCurrentPriorityLevel,wa=aa.unstable_ImmediatePriority,xa=aa.unstable_UserBlockingPriority,ya=aa.unstable_NormalPriority,za=aa.unstable_LowPriority,Aa=aa.unstable_IdlePriority,Ba=ba.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,Ca=null,Da=null,F=null,Ea="undefined"!==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__; diff --git a/packages/next/src/compiled/react/cjs/react-jsx-dev-runtime.development.js b/packages/next/src/compiled/react/cjs/react-jsx-dev-runtime.development.js index df5d9f15afcc7..f0ba5cf3696f4 100644 --- a/packages/next/src/compiled/react/cjs/react-jsx-dev-runtime.development.js +++ b/packages/next/src/compiled/react/cjs/react-jsx-dev-runtime.development.js @@ -14,7 +14,7 @@ if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; -var React = require('react'); +var React = require("next/dist/compiled/react"); // ATTENTION // When adding new symbols to this file, diff --git a/packages/next/src/compiled/react/cjs/react-jsx-runtime.development.js b/packages/next/src/compiled/react/cjs/react-jsx-runtime.development.js index cedb8f1b5ce3e..1b00c85f74658 100644 --- a/packages/next/src/compiled/react/cjs/react-jsx-runtime.development.js +++ b/packages/next/src/compiled/react/cjs/react-jsx-runtime.development.js @@ -14,7 +14,7 @@ if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; -var React = require('react'); +var React = require("next/dist/compiled/react"); // ATTENTION // When adding new symbols to this file, diff --git a/packages/next/src/compiled/react/cjs/react-jsx-runtime.production.min.js b/packages/next/src/compiled/react/cjs/react-jsx-runtime.production.min.js index 3ecb2350b3e55..2c15ab3ac1155 100644 --- a/packages/next/src/compiled/react/cjs/react-jsx-runtime.production.min.js +++ b/packages/next/src/compiled/react/cjs/react-jsx-runtime.production.min.js @@ -7,5 +7,5 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -'use strict';var f=require("react"),k=Symbol.for("react.element"),l=Symbol.for("react.fragment"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0}; +'use strict';var f=require("next/dist/compiled/react"),k=Symbol.for("react.element"),l=Symbol.for("react.fragment"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0}; function q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=""+g);void 0!==a.key&&(e=""+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a)void 0===d[b]&&(d[b]=a[b]);return{$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}exports.Fragment=l;exports.jsx=q;exports.jsxs=q; diff --git a/packages/next/src/compiled/react/cjs/react-jsx-runtime.profiling.min.js b/packages/next/src/compiled/react/cjs/react-jsx-runtime.profiling.min.js index b875a84b3d090..5936004d767cc 100644 --- a/packages/next/src/compiled/react/cjs/react-jsx-runtime.profiling.min.js +++ b/packages/next/src/compiled/react/cjs/react-jsx-runtime.profiling.min.js @@ -7,5 +7,5 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -'use strict';var f=require("react"),k=Symbol.for("react.element"),l=Symbol.for("react.fragment"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0}; +'use strict';var f=require("next/dist/compiled/react"),k=Symbol.for("react.element"),l=Symbol.for("react.fragment"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0}; function q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=""+g);void 0!==a.key&&(e=""+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a)void 0===d[b]&&(d[b]=a[b]);return{$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}exports.Fragment=l;exports.jsx=q;exports.jsxs=q; diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts index 71e36c5cd38b5..9fe376fc28e3a 100644 --- a/packages/next/src/export/index.ts +++ b/packages/next/src/export/index.ts @@ -7,6 +7,8 @@ import { readFileSync, writeFileSync, } from 'fs' + +import '../server/require-hook' import { Worker } from '../lib/worker' import { dirname, join, resolve, sep } from 'path' import { promisify } from 'util' @@ -47,20 +49,11 @@ import { isAPIRoute } from '../lib/is-api-route' import { getPagePath } from '../server/require' import { Span } from '../trace' import { FontConfig } from '../server/font-utils' -import { - loadRequireHook, - overrideBuiltInReactPackages, -} from '../build/webpack/require-hook' import { MiddlewareManifest } from '../build/webpack/plugins/middleware-plugin' import { isAppRouteRoute } from '../lib/is-app-route-route' import { isAppPageRoute } from '../lib/is-app-page-route' import isError from '../lib/is-error' -loadRequireHook() -if (process.env.NEXT_PREBUNDLED_REACT) { - overrideBuiltInReactPackages() -} - const exists = promisify(existsOrig) function divideSegments(number: number, segments: number): number[] { @@ -158,6 +151,7 @@ export interface ExportOptions { buildExport?: boolean statusMessage?: string exportPageWorker?: typeof import('./worker').default + exportAppPageWorker?: typeof import('./worker').default endWorker?: () => Promise nextConfig?: NextConfigComplete } @@ -652,9 +646,11 @@ export default async function exportApp( const timeout = nextConfig?.staticPageGenerationTimeout || 0 let infoPrinted = false let exportPage: typeof import('./worker').default + let exportAppPage: undefined | typeof import('./worker').default let endWorker: () => Promise if (options.exportPageWorker) { exportPage = options.exportPageWorker + exportAppPage = options.exportAppPageWorker endWorker = options.endWorker || (() => Promise.resolve()) } else { const worker = new Worker(require.resolve('./worker'), { @@ -696,7 +692,13 @@ export default async function exportApp( return pageExportSpan.traceAsyncFn(async () => { const pathMap = exportPathMap[path] - const result = await exportPage({ + const exportPageOrApp = pathMap._isAppDir ? exportAppPage : exportPage + if (!exportPageOrApp) { + throw new Error( + 'invariant: Undefined export worker for app dir, this is a bug in Next.js.' + ) + } + const result = await exportPageOrApp({ path, pathMap, distDir, diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts index becd111923645..6012d6e5f3747 100644 --- a/packages/next/src/export/worker.ts +++ b/packages/next/src/export/worker.ts @@ -10,17 +10,9 @@ import type { } from '../server/config-shared' import type { OutgoingHttpHeaders } from 'http' -// `NEXT_PREBUNDLED_REACT` env var is inherited from parent process, -// then override react packages here for export worker. -if (process.env.NEXT_PREBUNDLED_REACT) { - require('../build/webpack/require-hook').overrideBuiltInReactPackages() -} - // Polyfill fetch for the export worker. import '../server/node-polyfill-fetch' -import { loadRequireHook } from '../build/webpack/require-hook' - import { extname, join, dirname, sep, posix } from 'path' import fs, { promises } from 'fs' import AmpHtmlValidator from 'next/dist/compiled/amphtml-validator' @@ -54,8 +46,6 @@ import { toNodeHeaders } from '../server/web/utils' import { RouteModuleLoader } from '../server/future/helpers/module-loader/route-module-loader' import { NextRequestAdapter } from '../server/web/spec-extension/adapters/next-request' -loadRequireHook() - const envConfig = require('../shared/lib/runtime-config') ;(globalThis as any).__NEXT_DATA__ = { diff --git a/packages/next/src/server/config-utils.ts b/packages/next/src/server/config-utils.ts index f1995ab73d290..ebc7552f77eec 100644 --- a/packages/next/src/server/config-utils.ts +++ b/packages/next/src/server/config-utils.ts @@ -11,7 +11,7 @@ export function loadWebpackHook() { // hook the Node.js require so that webpack requires are // routed to the bundled and now initialized webpack version - require('../build/webpack/require-hook').loadRequireHook( + require('../server/require-hook').addHookAliases( [ ['webpack', 'next/dist/compiled/webpack/webpack-lib'], ['webpack/package', 'next/dist/compiled/webpack/package'], diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 3865b63d4d9ab..41dcf45f62dc7 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -549,7 +549,10 @@ export default class DevServer extends Server { } if (isAppPath) { - if (!validFileMatcher.isAppRouterPage(fileName)) { + if ( + !validFileMatcher.isAppRouterPage(fileName) && + !validFileMatcher.isRootNotFound(fileName) + ) { continue } // Ignore files/directories starting with `_` in the app directory diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts index 09ee5e276951d..bcfedebb8177c 100644 --- a/packages/next/src/server/dev/static-paths-worker.ts +++ b/packages/next/src/server/dev/static-paths-worker.ts @@ -1,6 +1,7 @@ import type { NextConfigComplete } from '../config-shared' import type { AppRouteUserlandModule } from '../future/route-modules/app-route/module' +import '../../server/require-hook' import '../node-polyfill-fetch' import { buildAppStaticPaths, @@ -10,21 +11,12 @@ import { } from '../../build/utils' import { loadComponents } from '../load-components' import { setHttpClientAndAgentOptions } from '../config' -import { - loadRequireHook, - overrideBuiltInReactPackages, -} from '../../build/webpack/require-hook' import { IncrementalCache } from '../lib/incremental-cache' import * as serverHooks from '../../client/components/hooks-server-context' import { staticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage' type RuntimeConfig = any -loadRequireHook() -if (process.env.NEXT_PREBUNDLED_REACT) { - overrideBuiltInReactPackages() -} - // expose AsyncLocalStorage on globalThis for react usage const { AsyncLocalStorage } = require('async_hooks') ;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage diff --git a/packages/next/src/server/initialize-require-hook.ts b/packages/next/src/server/initialize-require-hook.ts deleted file mode 100644 index 6de7004f37174..0000000000000 --- a/packages/next/src/server/initialize-require-hook.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - loadRequireHook, - overrideBuiltInReactPackages, -} from '../build/webpack/require-hook' - -loadRequireHook() - -const isPrebundled = false - -if (isPrebundled) { - overrideBuiltInReactPackages() -} diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index a4b11a54d3674..8b8d87f08ca4d 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -1,9 +1,14 @@ +import type { RequestHandler } from '../next' + import v8 from 'v8' import http from 'http' -import next from '../next' import { isIPv6 } from 'net' + +// This is required before other imports to ensure the require hook is setup. +import '../require-hook' + +import next from '../next' import { warn } from '../../build/output/log' -import type { RequestHandler } from '../next' import { deleteCache as _deleteCache, deleteAppClientCache as _deleteAppClientCache, diff --git a/packages/next/src/server/lib/server-ipc.ts b/packages/next/src/server/lib/server-ipc.ts index 1951a94c74384..fdb45847c275f 100644 --- a/packages/next/src/server/lib/server-ipc.ts +++ b/packages/next/src/server/lib/server-ipc.ts @@ -68,7 +68,7 @@ export const createWorker = ( serverPort: number, ipcPort: number, isNodeDebugging: boolean | 'brk' | undefined, - type: string + type: 'pages' | 'app' ) => { const { initialEnv } = require('@next/env') as typeof import('@next/env') const { Worker } = require('next/dist/compiled/jest-worker') @@ -88,6 +88,12 @@ export const createWorker = ( __NEXT_PRIVATE_RENDER_WORKER: type, __NEXT_PRIVATE_ROUTER_IPC_PORT: ipcPort + '', NODE_ENV: process.env.NODE_ENV, + ...(type === 'app' + ? { + NEXT_PREBUNDLED_REACT_WORKER: '1', + __NEXT_PRIVATE_PREBUNDLED_REACT: '1', + } + : {}), }, execArgv: genExecArgv( isNodeDebugging === undefined ? false : isNodeDebugging, diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 042d27c6e3882..3a5119cdceb63 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1,4 +1,4 @@ -import './initialize-require-hook' +import './require-hook' import './node-polyfill-fetch' import './node-polyfill-web-streams' @@ -1381,17 +1381,35 @@ export default class NextNodeServer extends BaseServer { if (this.isRouterWorker) { let page = pathname + let matched = false if (!(await this.hasPage(page))) { for (const route of this.dynamicRoutes || []) { if (route.match(pathname)) { page = route.page + matched = true break } } + } else { + matched = true } - const renderKind = this.appPathRoutes?.[page] ? 'app' : 'pages' + let renderKind: 'app' | 'pages' = this.appPathRoutes?.[page] + ? 'app' + : 'pages' + + // Handle app dir's /not-found feature: for 404 pages, they should be + // routed to the app renderer. + if (!matched && this.appPathRoutes) { + if ( + this.appPathRoutes[ + this.renderOpts.dev ? '/not-found' : '/_not-found' + ] + ) { + renderKind = 'app' + } + } if (this.renderWorkersPromises) { await this.renderWorkersPromises diff --git a/packages/next/src/server/next.ts b/packages/next/src/server/next.ts index a39d1dac18b2f..af997f4f227f6 100644 --- a/packages/next/src/server/next.ts +++ b/packages/next/src/server/next.ts @@ -3,6 +3,7 @@ import type { NodeRequestHandler } from './next-server' import type { UrlWithParsedQuery } from 'url' import type { NextConfigComplete } from './config-shared' +import './require-hook' import './node-polyfill-fetch' import { default as Server } from './next-server' import * as log from '../build/output/log' @@ -13,15 +14,9 @@ import { PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' import { PHASE_PRODUCTION_SERVER } from '../shared/lib/constants' import { IncomingMessage, ServerResponse } from 'http' import { NextUrlWithParsedQuery } from './request-meta' -import { - loadRequireHook, - overrideBuiltInReactPackages, -} from '../build/webpack/require-hook' import { getTracer } from './lib/trace/tracer' import { NextServerSpan } from './lib/trace/constants' -loadRequireHook() - let ServerImpl: typeof Server const getServerImpl = async () => { @@ -184,7 +179,6 @@ export class NextServer { } if (conf.experimental.appDir) { process.env.NEXT_PREBUNDLED_REACT = '1' - overrideBuiltInReactPackages() } this.server = await this.createServer({ ...this.options, diff --git a/packages/next/src/server/require-hook.ts b/packages/next/src/server/require-hook.ts new file mode 100644 index 0000000000000..e0fd2e270dee2 --- /dev/null +++ b/packages/next/src/server/require-hook.ts @@ -0,0 +1,88 @@ +// Synchronously inject a require hook for webpack and webpack/. It's required to use the internal ncc webpack version. +// This is needed for userland plugins to attach to the same webpack instance as Next.js'. +// Individually compiled modules are as defined for the compilation in bundles/webpack/packages/*. + +// This module will only be loaded once per process. + +const mod = require('module') +const resolveFilename = mod._resolveFilename +const hookPropertyMap = new Map() + +let aliasedPrebundledReact = false + +export function addHookAliases(aliases: [string, string][] = []) { + for (const [key, value] of aliases) { + hookPropertyMap.set(key, value) + } +} + +// Add default aliases +addHookAliases([ + // Use `require.resolve` explicitly to make them statically analyzable + // styled-jsx needs to be resolved as the external dependency. + ['styled-jsx', require.resolve('styled-jsx')], + ['styled-jsx/style', require.resolve('styled-jsx/style')], + ['styled-jsx/style', require.resolve('styled-jsx/style')], + ['server-only', require.resolve('next/dist/compiled/server-only')], + ['client-only', require.resolve('next/dist/compiled/client-only')], +]) + +// Override built-in React packages if necessary +function overrideReact() { + if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT) { + aliasedPrebundledReact = true + addHookAliases([ + ['react', require.resolve(`next/dist/compiled/react`)], + [ + 'react/jsx-runtime', + require.resolve(`next/dist/compiled/react/jsx-runtime`), + ], + [ + 'react/jsx-dev-runtime', + require.resolve(`next/dist/compiled/react/jsx-dev-runtime`), + ], + [ + 'react-dom', + require.resolve(`next/dist/compiled/react-dom/server-rendering-stub`), + ], + [ + 'react-dom/client', + require.resolve(`next/dist/compiled/react-dom/client`), + ], + [ + 'react-dom/server', + require.resolve(`next/dist/compiled/react-dom/server`), + ], + [ + 'react-dom/server.browser', + require.resolve(`next/dist/compiled/react-dom/server.browser`), + ], + ]) + } else { + addHookAliases([ + ['react/jsx-runtime', require.resolve(`react/jsx-runtime`)], + ['react/jsx-dev-runtime', require.resolve(`react/jsx-dev-runtime`)], + ]) + } +} +overrideReact() + +mod._resolveFilename = function ( + originalResolveFilename: typeof resolveFilename, + requestMap: Map, + request: string, + parent: any, + isMain: boolean, + options: any +) { + if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT && !aliasedPrebundledReact) { + // In case the environment variable is set after the module is loaded. + overrideReact() + } + + const hookResolved = requestMap.get(request) + if (hookResolved) request = hookResolved + return originalResolveFilename.call(mod, request, parent, isMain, options) + + // We use `bind` here to avoid referencing outside variables to create potential memory leaks. +}.bind(null, resolveFilename, hookPropertyMap) diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index fdfeab636127e..10ea3859181c4 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -1636,6 +1636,15 @@ export async function copy_vendor_react(task, opts) { await task.source(join(reactDir, 'LICENSE')).target(`src/compiled/react`) await task .source(join(reactDir, 'cjs/**/*.js')) + // eslint-disable-next-line require-yield + .run({ every: true }, function* (file) { + const source = file.data.toString() + // We replace the module/chunk loading code with our own implementation in Next.js. + file.data = source.replace( + /require\(["']react["']\)/g, + 'require("next/dist/compiled/react")' + ) + }) .target(`src/compiled/react/cjs`) await task @@ -1650,10 +1659,18 @@ export async function copy_vendor_react(task, opts) { .run({ every: true }, function* (file) { const source = file.data.toString() // We replace the module/chunk loading code with our own implementation in Next.js. - file.data = source.replace( - /require\(["']scheduler["']\)/g, - 'require("next/dist/compiled/scheduler")' - ) + file.data = source + .replace( + /require\(["']scheduler["']\)/g, + 'require("next/dist/compiled/scheduler")' + ) + .replace( + /require\(["']react["']\)/g, + 'require("next/dist/compiled/react")' + ) + + // Note that we don't replace `react-dom` with `next/dist/compiled/react-dom` + // as it mighe be aliased to the server rendering stub. }) .target(`src/compiled/react-dom/cjs`) diff --git a/test/development/app-render-error-log/app-render-error-log.test.ts b/test/development/app-render-error-log/app-render-error-log.test.ts index 47e4c761eb59c..cc3c98de1dd3e 100644 --- a/test/development/app-render-error-log/app-render-error-log.test.ts +++ b/test/development/app-render-error-log/app-render-error-log.test.ts @@ -16,9 +16,9 @@ createNextDescribe( await check(() => cliOutput, /digest:/) expect(cliOutput).toInclude('Error: boom') - expect(cliOutput).toInclude('at fn2 (./app/fn.ts:6:11)') - expect(cliOutput).toInclude('at fn1 (./app/fn.ts:9:5') - expect(cliOutput).toInclude('at Page (./app/page.tsx:10:45)') + expect(cliOutput).toInclude('at fn2 (./app/fn.ts') + expect(cliOutput).toInclude('at fn1 (./app/fn.ts') + expect(cliOutput).toInclude('at Page (./app/page.tsx') expect(cliOutput).not.toInclude('webpack-internal') }) @@ -32,9 +32,9 @@ createNextDescribe( await check(() => cliOutput, /digest:/) expect(cliOutput).toInclude('Error: boom') - expect(cliOutput).toInclude('at fn2 (./app/fn.ts:6:11)') - expect(cliOutput).toInclude('at fn1 (./app/fn.ts:9:5') - expect(cliOutput).toInclude('at EdgePage (./app/edge/page.tsx:12:45)') + expect(cliOutput).toInclude('at fn2 (./app/fn.ts') + expect(cliOutput).toInclude('at fn1 (./app/fn.ts') + expect(cliOutput).toInclude('at EdgePage (./app/edge/page.tsx') expect(cliOutput).not.toInclude('webpack-internal') })