UNPKG

next

Version:

The React Framework

273 lines (272 loc) • 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { RenderStage: null, StagedRenderingController: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { RenderStage: function() { return RenderStage; }, StagedRenderingController: function() { return StagedRenderingController; } }); const _invarianterror = require("../../shared/lib/invariant-error"); const _promisewithresolvers = require("../../shared/lib/promise-with-resolvers"); var RenderStage = /*#__PURE__*/ function(RenderStage) { RenderStage[RenderStage["Before"] = 1] = "Before"; RenderStage[RenderStage["Static"] = 2] = "Static"; RenderStage[RenderStage["Runtime"] = 3] = "Runtime"; RenderStage[RenderStage["Dynamic"] = 4] = "Dynamic"; RenderStage[RenderStage["Abandoned"] = 5] = "Abandoned"; return RenderStage; }({}); class StagedRenderingController { constructor(abortSignal = null, hasRuntimePrefetch){ this.abortSignal = abortSignal; this.hasRuntimePrefetch = hasRuntimePrefetch; this.currentStage = 1; this.staticInterruptReason = null; this.runtimeInterruptReason = null; this.staticStageEndTime = Infinity; this.runtimeStageEndTime = Infinity; this.runtimeStageListeners = []; this.dynamicStageListeners = []; this.runtimeStagePromise = (0, _promisewithresolvers.createPromiseWithResolvers)(); this.dynamicStagePromise = (0, _promisewithresolvers.createPromiseWithResolvers)(); this.mayAbandon = false; if (abortSignal) { abortSignal.addEventListener('abort', ()=>{ const { reason } = abortSignal; if (this.currentStage < 3) { this.runtimeStagePromise.promise.catch(ignoreReject) // avoid unhandled rejections ; this.runtimeStagePromise.reject(reason); } if (this.currentStage < 4 || this.currentStage === 5) { this.dynamicStagePromise.promise.catch(ignoreReject) // avoid unhandled rejections ; this.dynamicStagePromise.reject(reason); } }, { once: true }); this.mayAbandon = true; } } onStage(stage, callback) { if (this.currentStage >= stage) { callback(); } else if (stage === 3) { this.runtimeStageListeners.push(callback); } else if (stage === 4) { this.dynamicStageListeners.push(callback); } else { // This should never happen throw Object.defineProperty(new _invarianterror.InvariantError(`Invalid render stage: ${stage}`), "__NEXT_ERROR_CODE", { value: "E881", enumerable: false, configurable: true }); } } canSyncInterrupt() { // If we haven't started the render yet, it can't be interrupted. if (this.currentStage === 1) { return false; } const boundaryStage = this.hasRuntimePrefetch ? 4 : 3; return this.currentStage < boundaryStage; } syncInterruptCurrentStageWithReason(reason) { if (this.currentStage === 1) { return; } // If Sync IO occurs during the initial (abandonable) render, we'll retry it, // so we want a slightly different flow. // See the implementation of `abandonRenderImpl` for more explanation. if (this.mayAbandon) { return this.abandonRenderImpl(); } // If we're in the final render, we cannot abandon it. We need to advance to the Dynamic stage // and capture the interruption reason. switch(this.currentStage){ case 2: { this.staticInterruptReason = reason; this.advanceStage(4); return; } case 3: { // We only error for Sync IO in the runtime stage if the route // is configured to use runtime prefetching. // We do this to reflect the fact that during a runtime prefetch, // Sync IO aborts aborts the render. // Note that `canSyncInterrupt` should prevent us from getting here at all // if runtime prefetching isn't enabled. if (this.hasRuntimePrefetch) { this.runtimeInterruptReason = reason; this.advanceStage(4); } return; } case 4: case 5: default: } } getStaticInterruptReason() { return this.staticInterruptReason; } getRuntimeInterruptReason() { return this.runtimeInterruptReason; } getStaticStageEndTime() { return this.staticStageEndTime; } getRuntimeStageEndTime() { return this.runtimeStageEndTime; } abandonRender() { if (!this.mayAbandon) { throw Object.defineProperty(new _invarianterror.InvariantError('`abandonRender` called on a stage controller that cannot be abandoned.'), "__NEXT_ERROR_CODE", { value: "E938", enumerable: false, configurable: true }); } this.abandonRenderImpl(); } abandonRenderImpl() { // In staged rendering, only the initial render is abandonable. // We can abandon the initial render if // 1. We notice a cache miss, and need to wait for caches to fill // 2. A sync IO error occurs, and the render should be interrupted // (this might be a lazy intitialization of a module, // so we still want to restart in this case and see if it still occurs) // In either case, we'll be doing another render after this one, // so we only want to unblock the Runtime stage, not Dynamic, because // unblocking the dynamic stage would likely lead to wasted (uncached) IO. const { currentStage } = this; switch(currentStage){ case 2: { this.currentStage = 5; this.resolveRuntimeStage(); return; } case 3: { this.currentStage = 5; return; } case 4: case 1: case 5: break; default: { currentStage; } } } advanceStage(stage) { // If we're already at the target stage or beyond, do nothing. // (this can happen e.g. if sync IO advanced us to the dynamic stage) if (stage <= this.currentStage) { return; } let currentStage = this.currentStage; this.currentStage = stage; if (currentStage < 3 && stage >= 3) { this.staticStageEndTime = performance.now() + performance.timeOrigin; this.resolveRuntimeStage(); } if (currentStage < 4 && stage >= 4) { this.runtimeStageEndTime = performance.now() + performance.timeOrigin; this.resolveDynamicStage(); return; } } /** Fire the `onStage` listeners for the runtime stage and unblock any promises waiting for it. */ resolveRuntimeStage() { const runtimeListeners = this.runtimeStageListeners; for(let i = 0; i < runtimeListeners.length; i++){ runtimeListeners[i](); } runtimeListeners.length = 0; this.runtimeStagePromise.resolve(); } /** Fire the `onStage` listeners for the dynamic stage and unblock any promises waiting for it. */ resolveDynamicStage() { const dynamicListeners = this.dynamicStageListeners; for(let i = 0; i < dynamicListeners.length; i++){ dynamicListeners[i](); } dynamicListeners.length = 0; this.dynamicStagePromise.resolve(); } getStagePromise(stage) { switch(stage){ case 3: { return this.runtimeStagePromise.promise; } case 4: { return this.dynamicStagePromise.promise; } default: { stage; throw Object.defineProperty(new _invarianterror.InvariantError(`Invalid render stage: ${stage}`), "__NEXT_ERROR_CODE", { value: "E881", enumerable: false, configurable: true }); } } } waitForStage(stage) { return this.getStagePromise(stage); } delayUntilStage(stage, displayName, resolvedValue) { const ioTriggerPromise = this.getStagePromise(stage); const promise = makeDevtoolsIOPromiseFromIOTrigger(ioTriggerPromise, displayName, resolvedValue); // Analogously to `makeHangingPromise`, we might reject this promise if the signal is invoked. // (e.g. in the case where we don't want want the render to proceed to the dynamic stage and abort it). // We shouldn't consider this an unhandled rejection, so we attach a noop catch handler here to suppress this warning. if (this.abortSignal) { promise.catch(ignoreReject); } return promise; } } function ignoreReject() {} // TODO(restart-on-cache-miss): the layering of `delayUntilStage`, // `makeDevtoolsIOPromiseFromIOTrigger` and and `makeDevtoolsIOAwarePromise` // is confusing, we should clean it up. function makeDevtoolsIOPromiseFromIOTrigger(ioTrigger, displayName, resolvedValue) { // If we create a `new Promise` and give it a displayName // (with no userspace code above us in the stack) // React Devtools will use it as the IO cause when determining "suspended by". // In particular, it should shadow any inner IO that resolved/rejected the promise // (in case of staged rendering, this will be the `setTimeout` that triggers the relevant stage) const promise = new Promise((resolve, reject)=>{ ioTrigger.then(resolve.bind(null, resolvedValue), reject); }); if (displayName !== undefined) { // @ts-expect-error promise.displayName = displayName; } return promise; } //# sourceMappingURL=staged-rendering.js.map