UNPKG

next

Version:

The React Framework

334 lines (333 loc) • 15.6 kB
/** * The functions provided by this module are used to communicate certain properties * about the currently running code so that Next.js can make decisions on how to handle * the current execution in different rendering modes such as pre-rendering, resuming, and SSR. * * Today Next.js treats all code as potentially static. Certain APIs may only make sense when dynamically rendering. * Traditionally this meant deopting the entire render to dynamic however with PPR we can now deopt parts * of a React tree as dynamic while still keeping other parts static. There are really two different kinds of * Dynamic indications. * * The first is simply an intention to be dynamic. unstable_noStore is an example of this where * the currently executing code simply declares that the current scope is dynamic but if you use it * inside unstable_cache it can still be cached. This type of indication can be removed if we ever * make the default dynamic to begin with because the only way you would ever be static is inside * a cache scope which this indication does not affect. * * The second is an indication that a dynamic data source was read. This is a stronger form of dynamic * because it means that it is inappropriate to cache this at all. using a dynamic data source inside * unstable_cache should error. If you want to use some dynamic data inside unstable_cache you should * read that data outside the cache and pass it in as an argument to the cached function. */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { abortAndThrowOnSynchronousRequestDataAccess: null, abortOnSynchronousPlatformIOAccess: null, accessedDynamicData: null, annotateDynamicAccess: null, consumeDynamicAccess: null, createDynamicTrackingState: null, createHangingInputAbortSignal: null, formatDynamicAPIAccesses: null, getFirstDynamicReason: null, isPrerenderInterruptedError: null, markCurrentScopeAsDynamic: null, throwToInterruptStaticGeneration: null, trackDynamicDataInDynamicRender: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { abortAndThrowOnSynchronousRequestDataAccess: function() { return abortAndThrowOnSynchronousRequestDataAccess; }, abortOnSynchronousPlatformIOAccess: function() { return abortOnSynchronousPlatformIOAccess; }, accessedDynamicData: function() { return accessedDynamicData; }, annotateDynamicAccess: function() { return annotateDynamicAccess; }, consumeDynamicAccess: function() { return consumeDynamicAccess; }, createDynamicTrackingState: function() { return createDynamicTrackingState; }, createHangingInputAbortSignal: function() { return createHangingInputAbortSignal; }, formatDynamicAPIAccesses: function() { return formatDynamicAPIAccesses; }, getFirstDynamicReason: function() { return getFirstDynamicReason; }, isPrerenderInterruptedError: function() { return isPrerenderInterruptedError; }, markCurrentScopeAsDynamic: function() { return markCurrentScopeAsDynamic; }, throwToInterruptStaticGeneration: function() { return throwToInterruptStaticGeneration; }, trackDynamicDataInDynamicRender: function() { return trackDynamicDataInDynamicRender; } }); const _hooksservercontext = require("../../client/components/hooks-server-context"); const _staticgenerationbailout = require("../../client/components/static-generation-bailout"); const _workunitasyncstorageexternal = require("./work-unit-async-storage.external"); const _scheduler = require("../../lib/scheduler"); function createDynamicTrackingState(isDebugDynamicAccesses) { return { isDebugDynamicAccesses, dynamicAccesses: [], syncDynamicErrorWithStack: null, syncDynamicErrorWithStackPostMicrotask: false }; } function getFirstDynamicReason(trackingState) { var _trackingState_dynamicAccesses_; return (_trackingState_dynamicAccesses_ = trackingState.dynamicAccesses[0]) == null ? void 0 : _trackingState_dynamicAccesses_.expression; } function markCurrentScopeAsDynamic(store, workUnitStore, expression) { if (workUnitStore) { switch(workUnitStore.type){ case 'cache': case 'unstable-cache': // Inside cache scopes, marking a scope as dynamic has no effect, // because the outer cache scope creates a cache boundary. This is // subtly different from reading a dynamic data source, which is // forbidden inside a cache scope. return; case 'private-cache': // A private cache scope is already dynamic by definition. return; case 'prerender-legacy': case 'request': case 'build-time-generator': break; default: workUnitStore; } } // If we're forcing dynamic rendering or we're forcing static rendering, we // don't need to do anything here because the entire page is already dynamic // or it's static and it should not throw or postpone here. if (store.forceDynamic || store.forceStatic) return; if (store.dynamicShouldError) { throw new _staticgenerationbailout.StaticGenBailoutError(`Route ${store.route} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`); } if (workUnitStore) { switch(workUnitStore.type){ case 'prerender-legacy': workUnitStore.revalidate = 0; // We aren't prerendering, but we are generating a static page. We need // to bail out of static generation. const err = new _hooksservercontext.DynamicServerError(`Route ${store.route} couldn't be rendered statically because it used ${expression}. See more info here: https://nextjs.org/docs/messages/dynamic-server-error`); store.dynamicUsageDescription = expression; store.dynamicUsageStack = err.stack; throw err; case 'request': markRequestStoreAsDynamic(workUnitStore); break; case 'build-time-generator': break; default: workUnitStore; } } } function throwToInterruptStaticGeneration(expression, store, prerenderStore) { // We aren't prerendering but we are generating a static page. We need to bail out of static generation const err = new _hooksservercontext.DynamicServerError(`Route ${store.route} couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error`); prerenderStore.revalidate = 0; store.dynamicUsageDescription = expression; store.dynamicUsageStack = err.stack; throw err; } function trackDynamicDataInDynamicRender(workUnitStore) { switch(workUnitStore.type){ case 'cache': case 'unstable-cache': // Inside cache scopes, marking a scope as dynamic has no effect, // because the outer cache scope creates a cache boundary. This is // subtly different from reading a dynamic data source, which is // forbidden inside a cache scope. return; case 'private-cache': // A private cache scope is already dynamic by definition. return; case 'prerender': case 'prerender-runtime': case 'prerender-legacy': case 'prerender-client': case 'validation-client': case 'build-time-generator': break; case 'request': markRequestStoreAsDynamic(workUnitStore); break; default: workUnitStore; } } function markRequestStoreAsDynamic(requestStore) { if (process.env.__NEXT_DEV_SERVER && requestStore.phase === 'render') { requestStore.usedDynamic = true; } } function abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore) { const reason = `Route ${route} needs to bail out of prerendering at this point because it used ${expression}.`; const error = createPrerenderInterruptedError(reason); prerenderStore.controller.abort(error); const dynamicTracking = prerenderStore.dynamicTracking; if (dynamicTracking) { dynamicTracking.dynamicAccesses.push({ // When we aren't debugging, we don't need to create another error for the // stack trace. stack: dynamicTracking.isDebugDynamicAccesses ? new Error().stack : undefined, expression }); } } function abortOnSynchronousPlatformIOAccess(route, expression, errorWithStack, prerenderStore) { const dynamicTracking = prerenderStore.dynamicTracking; if (dynamicTracking && dynamicTracking.syncDynamicErrorWithStack === null) { dynamicTracking.syncDynamicErrorWithStack = errorWithStack; // React completes the task that is currently rendering before scheduled // abort cleanup. Client tracking can attribute the sync IO only during // that current task; server tracking keeps the error regardless. queueMicrotask(()=>{ dynamicTracking.syncDynamicErrorWithStackPostMicrotask = true; }); } abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore); } function abortAndThrowOnSynchronousRequestDataAccess(route, expression, errorWithStack, prerenderStore) { const prerenderSignal = prerenderStore.controller.signal; if (prerenderSignal.aborted === false) { // TODO it would be better to move this aborted check into the callsite so we can avoid making // the error object when it isn't relevant to the aborting of the prerender however // since we need the throw semantics regardless of whether we abort it is easier to land // this way. See how this was handled with `abortOnSynchronousPlatformIOAccess` for a closer // to ideal implementation abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore); // Preserve the exact server-side dynamic access for final validation after // interrupting this render. const dynamicTracking = prerenderStore.dynamicTracking; if (dynamicTracking) { if (dynamicTracking.syncDynamicErrorWithStack === null) { dynamicTracking.syncDynamicErrorWithStack = errorWithStack; } } } throw createPrerenderInterruptedError(`Route ${route} needs to bail out of prerendering at this point because it used ${expression}.`); } const NEXT_PRERENDER_INTERRUPTED = 'NEXT_PRERENDER_INTERRUPTED'; function createPrerenderInterruptedError(message) { const error = new Error(message); error.digest = NEXT_PRERENDER_INTERRUPTED; return error; } function isPrerenderInterruptedError(error) { return typeof error === 'object' && error !== null && error.digest === NEXT_PRERENDER_INTERRUPTED && 'name' in error && 'message' in error && error instanceof Error; } function accessedDynamicData(dynamicAccesses) { return dynamicAccesses.length > 0; } function consumeDynamicAccess(serverDynamic, clientDynamic) { // We mutate because we only call this once we are no longer writing // to the dynamicTrackingState and it's more efficient than creating a new // array. serverDynamic.dynamicAccesses.push(...clientDynamic.dynamicAccesses); return serverDynamic.dynamicAccesses; } function formatDynamicAPIAccesses(dynamicAccesses) { return dynamicAccesses.filter((access)=>typeof access.stack === 'string' && access.stack.length > 0).map(({ expression, stack })=>{ stack = stack.split('\n')// Remove the "Error: " prefix from the first line of the stack trace as // well as the first 4 lines of the stack trace which is the distance // from the user code and the `new Error().stack` call. .slice(4).filter((line)=>{ // Exclude Next.js internals from the stack trace. if (line.includes('node_modules/next/')) { return false; } // Exclude anonymous functions from the stack trace. if (line.includes(' (<anonymous>)')) { return false; } // Exclude Node.js internals from the stack trace. if (line.includes(' (node:')) { return false; } return true; }).join('\n'); return `Dynamic API Usage Debug - ${expression}:\n${stack}`; }); } function createHangingInputAbortSignal(workUnitStore) { switch(workUnitStore.type){ case 'prerender': case 'prerender-runtime': const controller = new AbortController(); if (workUnitStore.cacheSignal) { // If we have a cacheSignal it means we're in a prospective render. If // the input we're waiting on is coming from another cache, we do want // to wait for it so that we can resolve this cache entry too. workUnitStore.cacheSignal.inputReady().then(()=>{ controller.abort(); }); } else { // Otherwise we're in the final render and we should already have all // our caches filled. // If the prerender uses stages, we have wait until the final stage. // if an input didn't resolve at that point, then we can assume it never will. // // We might still be waiting on some microtasks so we // wait one tick before giving up. When we give up, we still want to // render the content of this cache as deeply as we can so that we can // suspend as deeply as possible in the tree or not at all if we don't // end up waiting for the input. const stagedRendering = (0, _workunitasyncstorageexternal.getStagedRenderingController)(workUnitStore); if (stagedRendering && stagedRendering.finalStage !== null) { stagedRendering.waitForStage(stagedRendering.finalStage).then(()=>(0, _scheduler.scheduleOnNextTick)(()=>controller.abort()), noop); } else { (0, _scheduler.scheduleOnNextTick)(()=>controller.abort()); } } return controller.signal; case 'prerender-client': case 'validation-client': case 'prerender-legacy': case 'request': case 'cache': case 'private-cache': case 'unstable-cache': case 'build-time-generator': return undefined; default: workUnitStore; } } function noop() {} function annotateDynamicAccess(expression, prerenderStore) { const dynamicTracking = prerenderStore.dynamicTracking; if (dynamicTracking) { dynamicTracking.dynamicAccesses.push({ stack: dynamicTracking.isDebugDynamicAccesses ? new Error().stack : undefined, expression }); } } //# sourceMappingURL=dynamic-rendering.js.map