diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 8d851d7066270..ee5e6e75ee870 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -955,7 +955,6 @@ async function prospectiveRuntimeServerPrerender( renderResumeDataCache, prerenderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // We only need task sequencing in the final prerender. runtimeStagePromise: null, // These are not present in regular prerenders, but allowed in a runtime prerender. @@ -1092,7 +1091,6 @@ async function finalRuntimeServerPrerender( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Used to separate the "Static" stage from the "Runtime" stage. runtimeStagePromise, // These are not present in regular prerenders, but allowed in a runtime prerender. @@ -3315,9 +3313,7 @@ async function spawnDynamicValidationInDev( // ready to cut the render off. const cacheSignal = new CacheSignal() - const captureOwnerStackClient = ReactClient.captureOwnerStack - const { captureOwnerStack: captureOwnerStackServer, createElement } = - ComponentMod + const { createElement } = ComponentMod // The resume data cache here should use a fresh instance as it's // performing a fresh prerender. If we get to implementing the @@ -3350,7 +3346,6 @@ async function spawnDynamicValidationInDev( prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash, - captureOwnerStack: captureOwnerStackServer, } // We're not going to use the result of this render because the only time it could be used @@ -3384,7 +3379,6 @@ async function spawnDynamicValidationInDev( prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash, - captureOwnerStack: captureOwnerStackServer, } const pendingInitialServerResult = workUnitAsyncStorage.run( @@ -3505,7 +3499,6 @@ async function spawnDynamicValidationInDev( prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash: undefined, - captureOwnerStack: captureOwnerStackClient, } const prerender = ( @@ -3616,7 +3609,6 @@ async function spawnDynamicValidationInDev( prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash, - captureOwnerStack: captureOwnerStackServer, } const finalAttemptRSCPayload = await workUnitAsyncStorage.run( @@ -3650,7 +3642,6 @@ async function spawnDynamicValidationInDev( prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash, - captureOwnerStack: captureOwnerStackServer, } const reactServerResult = await createReactServerPrerenderResult( @@ -3730,7 +3721,6 @@ async function spawnDynamicValidationInDev( prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash, - captureOwnerStack: captureOwnerStackClient, } let dynamicValidation = createDynamicValidationState() @@ -4108,7 +4098,6 @@ async function prerenderToStream( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Not available in production. } // We're not going to use the result of this render because the only time it could be used @@ -4142,7 +4131,6 @@ async function prerenderToStream( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Not available in production. }) const pendingInitialServerResult = workUnitAsyncStorage.run( @@ -4257,7 +4245,6 @@ async function prerenderToStream( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Not available in production. } const prerender = ( @@ -4367,7 +4354,6 @@ async function prerenderToStream( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Not available in production. } const finalAttemptRSCPayload = await workUnitAsyncStorage.run( @@ -4402,7 +4388,6 @@ async function prerenderToStream( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Not available in production. }) let prerenderIsPending = true @@ -4488,7 +4473,6 @@ async function prerenderToStream( prerenderResumeDataCache, renderResumeDataCache, hmrRefreshHash: undefined, - captureOwnerStack: undefined, // Not available in production. } let dynamicValidation = createDynamicValidationState() diff --git a/packages/next/src/server/app-render/work-unit-async-storage.external.ts b/packages/next/src/server/app-render/work-unit-async-storage.external.ts index 679dc6766fbe4..d321cbc008093 100644 --- a/packages/next/src/server/app-render/work-unit-async-storage.external.ts +++ b/packages/next/src/server/app-render/work-unit-async-storage.external.ts @@ -207,11 +207,6 @@ interface PrerenderStoreModernCommon * subsequent dynamic render. */ readonly hmrRefreshHash: string | undefined - - /** - * Only available in dev mode. - */ - readonly captureOwnerStack: undefined | (() => string | null) } interface StaticPrerenderStoreCommon { diff --git a/packages/next/src/server/node-environment-extensions/console-dim.external.test.ts b/packages/next/src/server/node-environment-extensions/console-dim.external.test.ts index 6fa27fd78f515..2a0bade809b03 100644 --- a/packages/next/src/server/node-environment-extensions/console-dim.external.test.ts +++ b/packages/next/src/server/node-environment-extensions/console-dim.external.test.ts @@ -321,13 +321,23 @@ describe('console-exit patches', () => { } // Install patches - this wraps the current console.log - const { - registerGetCacheSignal, - } = require('next/dist/server/node-environment-extensions/console-dim.external') + require('next/dist/server/node-environment-extensions/console-dim.external') - registerGetCacheSignal(() => null) - registerGetCacheSignal(() => controller?.signal) - registerGetCacheSignal(() => null) + const { + registerServerReact, + registerClientReact, + } = require('next/dist/server/runtime-reacts.external') + + registerServerReact({ + cacheSignal() { + return controller?.signal + }, + }) + registerClientReact({ + cacheSignal() { + return null + }, + }) console.log('before abort') controller.abort() diff --git a/packages/next/src/server/node-environment-extensions/console-dim.external.tsx b/packages/next/src/server/node-environment-extensions/console-dim.external.tsx index 2f83c1e820aea..87396a043c8d3 100644 --- a/packages/next/src/server/node-environment-extensions/console-dim.external.tsx +++ b/packages/next/src/server/node-environment-extensions/console-dim.external.tsx @@ -4,12 +4,7 @@ import { type ConsoleStore, } from '../app-render/console-async-storage.external' import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external' - -type GetCacheSignal = () => AbortSignal | null -const cacheSignals: Array = [] -export function registerGetCacheSignal(getSignal: GetCacheSignal): void { - cacheSignals.push(getSignal) -} +import { getServerReact, getClientReact } from '../runtime-reacts.external' // eslint-disable-next-line @typescript-eslint/no-unused-vars -- we may use later and want parity with the HIDDEN_STYLE value const DIMMED_STYLE = 'dimmed' @@ -191,33 +186,32 @@ function patchConsoleMethod(methodName: InterceptableConsoleMethod): void { // the server React cacheSignal implementation. Any particular console call will be in one, the other, or neither // scope and these signals return null if you are out of scope so this can be called from a single global patch // and still work properly. - for (let i = 0; i < cacheSignals.length; i++) { - const signal = cacheSignals[i]() // try to get a signal from registered functions - if (signal) { - // We are in a React Server render and can consult the React cache signal to determine if logs - // are now dimmable. - if (signal.aborted) { - if (currentAbortedLogsStyle === HIDDEN_STYLE) { - return - } - return applyWithDimming.call( - this, - consoleStore, - originalMethod, - methodName, - args - ) - } else if (consoleStore?.dim === true) { - return applyWithDimming.call( - this, - consoleStore, - originalMethod, - methodName, - args - ) - } else { - return originalMethod.apply(this, args) + const signal = + getClientReact()?.cacheSignal() ?? getServerReact()?.cacheSignal() + if (signal) { + // We are in a React Server render and can consult the React cache signal to determine if logs + // are now dimmable. + if (signal.aborted) { + if (currentAbortedLogsStyle === HIDDEN_STYLE) { + return } + return applyWithDimming.call( + this, + consoleStore, + originalMethod, + methodName, + args + ) + } else if (consoleStore?.dim === true) { + return applyWithDimming.call( + this, + consoleStore, + originalMethod, + methodName, + args + ) + } else { + return originalMethod.apply(this, args) } } diff --git a/packages/next/src/server/node-environment-extensions/utils.tsx b/packages/next/src/server/node-environment-extensions/utils.tsx index 3b7840bd186cb..30213664c9bf0 100644 --- a/packages/next/src/server/node-environment-extensions/utils.tsx +++ b/packages/next/src/server/node-environment-extensions/utils.tsx @@ -1,14 +1,13 @@ import { workAsyncStorage } from '../app-render/work-async-storage.external' -import { - workUnitAsyncStorage, - type PrerenderStoreModern, -} from '../app-render/work-unit-async-storage.external' +import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external' import { abortOnSynchronousPlatformIOAccess, trackSynchronousPlatformIOAccessInDev, } from '../app-render/dynamic-rendering' import { InvariantError } from '../../shared/lib/invariant-error' +import { getServerReact, getClientReact } from '../runtime-reacts.external' + type ApiType = 'time' | 'random' | 'crypto' export function io(expression: string, type: ApiType) { @@ -47,7 +46,7 @@ export function io(expression: string, type: ApiType) { abortOnSynchronousPlatformIOAccess( workStore.route, expression, - applyOwnerStack(new Error(message), workUnitStore), + applyOwnerStack(new Error(message)), workUnitStore ) } @@ -79,7 +78,7 @@ export function io(expression: string, type: ApiType) { abortOnSynchronousPlatformIOAccess( workStore.route, expression, - applyOwnerStack(new Error(message), workUnitStore), + applyOwnerStack(new Error(message)), workUnitStore ) } @@ -101,16 +100,15 @@ export function io(expression: string, type: ApiType) { } } -function applyOwnerStack(error: Error, workUnitStore: PrerenderStoreModern) { +function applyOwnerStack(error: Error) { // TODO: Instead of stitching the stacks here, we should log the original // error as-is when it occurs, and let `patchErrorInspect` handle adding the // owner stack, instead of logging it deferred in the `LogSafely` component // via `throwIfDisallowedDynamic`. - if ( - process.env.NODE_ENV !== 'production' && - workUnitStore.captureOwnerStack - ) { - const ownerStack = workUnitStore.captureOwnerStack() + if (process.env.NODE_ENV !== 'production') { + const ownerStack = + getClientReact()?.captureOwnerStack() ?? + getServerReact()?.captureOwnerStack() if (ownerStack) { let stack = ownerStack diff --git a/packages/next/src/server/route-modules/app-page/module.ts b/packages/next/src/server/route-modules/app-page/module.ts index 58d4b773718aa..44968511476bd 100644 --- a/packages/next/src/server/route-modules/app-page/module.ts +++ b/packages/next/src/server/route-modules/app-page/module.ts @@ -39,13 +39,12 @@ if (process.env.NEXT_RUNTIME !== 'edge') { vendoredReactSSR = require('./vendored/ssr/entrypoints') as typeof import('./vendored/ssr/entrypoints') - // In Node environments we augment console logging with information contextual to a React render. - // This patching is global so we need to register the cacheSignal getter from our bundled React instances - // here when we load them rather than in the external module itself when the patch is applied. - const { registerGetCacheSignal } = - require('../../node-environment-extensions/console-dim.external') as typeof import('../../node-environment-extensions/console-dim.external') - registerGetCacheSignal(vendoredReactRSC.React.cacheSignal) - registerGetCacheSignal(vendoredReactSSR.React.cacheSignal) + // In Node environments we need to access the correct React instance from external modules such + // as global patches. We register the loaded React instances here. + const { registerServerReact, registerClientReact } = + require('../../runtime-reacts.external') as typeof import('../../runtime-reacts.external') + registerServerReact(vendoredReactRSC.React) + registerClientReact(vendoredReactSSR.React) } /** diff --git a/packages/next/src/server/route-modules/app-route/module.ts b/packages/next/src/server/route-modules/app-route/module.ts index c10944bd0d9a5..ae9e62bfb290e 100644 --- a/packages/next/src/server/route-modules/app-route/module.ts +++ b/packages/next/src/server/route-modules/app-route/module.ts @@ -413,7 +413,6 @@ export class AppRouteRouteModule extends RouteModule< prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash: undefined, - captureOwnerStack: undefined, }) let prospectiveResult @@ -505,7 +504,6 @@ export class AppRouteRouteModule extends RouteModule< prerenderResumeDataCache, renderResumeDataCache: null, hmrRefreshHash: undefined, - captureOwnerStack: undefined, }) let responseHandled = false diff --git a/packages/next/src/server/runtime-reacts.external.ts b/packages/next/src/server/runtime-reacts.external.ts new file mode 100644 index 0000000000000..fce6fde59ec51 --- /dev/null +++ b/packages/next/src/server/runtime-reacts.external.ts @@ -0,0 +1,15 @@ +let ClientReact: typeof import('react') | null = null +export function registerClientReact(react: typeof import('react')) { + ClientReact = react +} +export function getClientReact() { + return ClientReact +} + +let ServerReact: typeof import('react') | null = null +export function registerServerReact(react: typeof import('react')) { + ServerReact = react +} +export function getServerReact() { + return ServerReact +} diff --git a/test/production/next-server-nft/next-server-nft.test.ts b/test/production/next-server-nft/next-server-nft.test.ts index 36f0d665bc3a9..4f59f42c3717f 100644 --- a/test/production/next-server-nft/next-server-nft.test.ts +++ b/test/production/next-server-nft/next-server-nft.test.ts @@ -321,6 +321,7 @@ const isReact18 = parseInt(process.env.NEXT_TEST_REACT_VERSION) === 18 "/node_modules/next/dist/server/route-modules/pages/vendored/contexts/loadable.js", "/node_modules/next/dist/server/route-modules/pages/vendored/contexts/router-context.js", "/node_modules/next/dist/server/route-modules/pages/vendored/contexts/server-inserted-html.js", + "/node_modules/next/dist/server/runtime-reacts.external.js", "/node_modules/next/dist/shared/lib/deep-freeze.js", "/node_modules/next/dist/shared/lib/invariant-error.js", "/node_modules/next/dist/shared/lib/is-plain-object.js",