From 3e2ac8211a3f0cf6babb485b0259bb07462daa34 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 8 Sep 2022 17:27:29 +0200 Subject: [PATCH] Update react-server-dom-webpack --- package.json | 4 +- ...bpack-writer.browser.development.server.js | 212 +++++++++++++++++- ...ck-writer.browser.production.min.server.js | 72 +++--- .../react-server-dom-webpack.development.js | 9 +- ...react-server-dom-webpack.production.min.js | 4 +- packages/next/package.json | 2 +- pnpm-lock.yaml | 34 +-- 7 files changed, 267 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 5f5ac0dfcb40dcb..758a6b8200aff4e 100644 --- a/package.json +++ b/package.json @@ -177,8 +177,8 @@ "react-17": "npm:react@17.0.2", "react-dom": "18.2.0", "react-dom-17": "npm:react-dom@17.0.2", - "react-dom-exp": "npm:react-dom@0.0.0-experimental-0de3ddf56-20220825", - "react-exp": "npm:react@0.0.0-experimental-0de3ddf56-20220825", + "react-dom-exp": "npm:react-dom@0.0.0-experimental-7028ce745-20220907", + "react-exp": "npm:react@0.0.0-experimental-7028ce745-20220907", "react-ssr-prepass": "1.0.8", "react-virtualized": "9.22.3", "relay-compiler": "13.0.2", diff --git a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js index a4dfaa018c81e2a..b5e1d259afd5267 100644 --- a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +++ b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js @@ -894,18 +894,109 @@ function readContext(context) { return value; } +// Corresponds to ReactFiberWakeable module. Generally, changes to one module +// should be reflected in the other. +// TODO: Rename this module and the corresponding Fiber one to "Thenable" +// instead of "Wakeable". Or some other more appropriate name. +// TODO: Sparse arrays are bad for performance. +function createThenableState() { + // The ThenableState is created the first time a component suspends. If it + // suspends again, we'll reuse the same state. + return []; +} +function trackSuspendedWakeable(wakeable) { + // If this wakeable isn't already a thenable, turn it into one now. Then, + // when we resume the work loop, we can check if its status is + // still pending. + // TODO: Get rid of the Wakeable type? It's superseded by UntrackedThenable. + var thenable = wakeable; // We use an expando to track the status and result of a thenable so that we + // can synchronously unwrap the value. Think of this as an extension of the + // Promise API, or a custom interface that is a superset of Thenable. + // + // If the thenable doesn't have a status, set it to "pending" and attach + // a listener that will update its status and result when it resolves. + + switch (thenable.status) { + case 'pending': + // Since the status is already "pending", we can assume it will be updated + // when it resolves, either by React or something in userspace. + break; + + case 'fulfilled': + case 'rejected': + // A thenable that already resolved shouldn't have been thrown, so this is + // unexpected. Suggests a mistake in a userspace data library. Don't track + // this thenable, because if we keep trying it will likely infinite loop + // without ever resolving. + // TODO: Log a warning? + break; + + default: + { + // TODO: Only instrument the thenable if the status if not defined. If + // it's defined, but an unknown value, assume it's been instrumented by + // some custom userspace implementation. + var pendingThenable = thenable; + pendingThenable.status = 'pending'; + pendingThenable.then(function (fulfilledValue) { + if (thenable.status === 'pending') { + var fulfilledThenable = thenable; + fulfilledThenable.status = 'fulfilled'; + fulfilledThenable.value = fulfilledValue; + } + }, function (error) { + if (thenable.status === 'pending') { + var rejectedThenable = thenable; + rejectedThenable.status = 'rejected'; + rejectedThenable.reason = error; + } + }); + break; + } + } +} +function trackUsedThenable(thenableState, thenable, index) { + // This is only a separate function from trackSuspendedWakeable for symmetry + // with Fiber. + // TODO: Disallow throwing a thenable directly. It must go through `use` (or + // some equivalent for internal Suspense implementations). We can't do this in + // Fiber yet because it's a breaking change but we can do it in Server + // Components because Server Components aren't released yet. + thenableState[index] = thenable; +} +function getPreviouslyUsedThenableAtIndex(thenableState, index) { + if (thenableState !== null) { + var thenable = thenableState[index]; + + if (thenable !== undefined) { + return thenable; + } + } + + return null; +} + var currentRequest = null; +var thenableIndexCounter = 0; +var thenableState = null; function prepareToUseHooksForRequest(request) { currentRequest = request; } function resetHooksForRequest() { currentRequest = null; } +function prepareToUseHooksForComponent(prevThenableState) { + thenableIndexCounter = 0; + thenableState = prevThenableState; +} +function getThenableStateAfterSuspending() { + return thenableState; +} function readContext$1(context) { { if (context.$$typeof !== REACT_SERVER_CONTEXT_TYPE) { - error('Only ServerContext is supported in Flight'); + error('Only createServerContext is supported in Server Components.'); } if (currentCache === null) { @@ -958,7 +1049,8 @@ var Dispatcher = { }, useMemoCache: function (size) { return new Array(size); - } + }, + use: use }; function unsupportedHook() { @@ -990,6 +1082,80 @@ function useId() { return ':' + currentRequest.identifierPrefix + 'S' + id.toString(32) + ':'; } +function use(usable) { + if (usable !== null && typeof usable === 'object') { + if (typeof usable.then === 'function') { + // This is a thenable. + var thenable = usable; // Track the position of the thenable within this fiber. + + var index = thenableIndexCounter; + thenableIndexCounter += 1; + + switch (thenable.status) { + case 'fulfilled': + { + var fulfilledValue = thenable.value; + return fulfilledValue; + } + + case 'rejected': + { + var rejectedError = thenable.reason; + throw rejectedError; + } + + default: + { + var prevThenableAtIndex = getPreviouslyUsedThenableAtIndex(thenableState, index); + + if (prevThenableAtIndex !== null) { + switch (prevThenableAtIndex.status) { + case 'fulfilled': + { + var _fulfilledValue = prevThenableAtIndex.value; + return _fulfilledValue; + } + + case 'rejected': + { + var _rejectedError = prevThenableAtIndex.reason; + throw _rejectedError; + } + + default: + { + // The thenable still hasn't resolved. Suspend with the same + // thenable as last time to avoid redundant listeners. + throw prevThenableAtIndex; + } + } + } else { + // This is the first time something has been used at this index. + // Stash the thenable at the current index so we can reuse it during + // the next attempt. + if (thenableState === null) { + thenableState = createThenableState(); + } + + trackUsedThenable(thenableState, thenable, index); // Suspend. + // TODO: Throwing here is an implementation detail that allows us to + // unwind the call stack. But we shouldn't allow it to leak into + // userspace. Throw an opaque placeholder value instead of the + // actual thenable. If it doesn't get captured by the work loop, log + // a warning, because that means something in userspace must have + // caught it. + + throw thenable; + } + } + } + } + } // eslint-disable-next-line react-internal/safe-string-coercion + + + throw new Error('An unsupported type was passed to use(): ' + String(usable)); +} + var ContextRegistry = ReactSharedInternals.ContextRegistry; function getOrCreateServerContext(globalName) { if (!ContextRegistry[globalName]) { @@ -1051,7 +1217,7 @@ function createRootContext(reqContext) { var POP = {}; -function attemptResolveElement(type, key, ref, props) { +function attemptResolveElement(type, key, ref, props, prevThenableState) { if (ref !== null && ref !== undefined) { // When the ref moves to the regular props object this will implicitly // throw for functions. We could probably relax it to a DEV warning for other @@ -1066,6 +1232,7 @@ function attemptResolveElement(type, key, ref, props) { } // This is a server-side component. + prepareToUseHooksForComponent(prevThenableState); return type(props); } else if (typeof type === 'string') { // This is a host element. E.g. HTML. @@ -1094,18 +1261,19 @@ function attemptResolveElement(type, key, ref, props) { var payload = type._payload; var init = type._init; var wrappedType = init(payload); - return attemptResolveElement(wrappedType, key, ref, props); + return attemptResolveElement(wrappedType, key, ref, props, prevThenableState); } case REACT_FORWARD_REF_TYPE: { var render = type.render; + prepareToUseHooksForComponent(prevThenableState); return render(props, undefined); } case REACT_MEMO_TYPE: { - return attemptResolveElement(type.type, key, ref, props); + return attemptResolveElement(type.type, key, ref, props, prevThenableState); } case REACT_PROVIDER_TYPE: @@ -1159,7 +1327,8 @@ function createTask(request, model, context, abortSet) { context: context, ping: function () { return pingTask(request, task); - } + }, + thenableState: null }; abortSet.add(task); return task; @@ -1426,7 +1595,7 @@ function resolveModelToJSON(request, parent, key, value) { // TODO: Concatenate keys of parents onto children. var element = value; // Attempt to render the server component. - value = attemptResolveElement(element.type, element.key, element.ref, element.props); + value = attemptResolveElement(element.type, element.key, element.ref, element.props, null); break; } @@ -1445,6 +1614,9 @@ function resolveModelToJSON(request, parent, key, value) { var newTask = createTask(request, value, getActiveContext(), request.abortableTasks); var ping = newTask.ping; x.then(ping, ping); + var wakeable = x; + trackSuspendedWakeable(wakeable); + newTask.thenableState = getThenableStateAfterSuspending(); return serializeByRefID(newTask.id); } else { logRecoverableError(request, x); // Something errored. We'll still send everything we have up until this point. @@ -1625,14 +1797,29 @@ function retryTask(request, task) { try { var _value3 = task.model; - while (typeof _value3 === 'object' && _value3 !== null && _value3.$$typeof === REACT_ELEMENT_TYPE) { + if (typeof _value3 === 'object' && _value3 !== null && _value3.$$typeof === REACT_ELEMENT_TYPE) { // TODO: Concatenate keys of parents onto children. - var element = _value3; // Attempt to render the server component. + var element = _value3; // When retrying a component, reuse the thenableState from the + // previous attempt. + + var prevThenableState = task.thenableState; // Attempt to render the server component. // Doing this here lets us reuse this same task if the next component // also suspends. task.model = _value3; - _value3 = attemptResolveElement(element.type, element.key, element.ref, element.props); + _value3 = attemptResolveElement(element.type, element.key, element.ref, element.props, prevThenableState); // Successfully finished this component. We're going to keep rendering + // using the same task, but we reset its thenable state before continuing. + + task.thenableState = null; // Keep rendering and reuse the same task. This inner loop is separate + // from the render above because we don't need to reset the thenable state + // until the next time something suspends and retries. + + while (typeof _value3 === 'object' && _value3 !== null && _value3.$$typeof === REACT_ELEMENT_TYPE) { + // TODO: Concatenate keys of parents onto children. + var nextElement = _value3; + task.model = _value3; + _value3 = attemptResolveElement(nextElement.type, nextElement.key, nextElement.ref, nextElement.props, null); + } } var processedChunk = processModelChunk(request, task.id, _value3); @@ -1644,6 +1831,9 @@ function retryTask(request, task) { // Something suspended again, let's pick it back up later. var ping = task.ping; x.then(ping, ping); + var wakeable = x; + trackSuspendedWakeable(wakeable); + task.thenableState = getThenableStateAfterSuspending(); return; } else { request.abortableTasks.delete(task); @@ -1690,7 +1880,7 @@ function abortTask(task, request, errorId) { var ref = serializeByValueID(errorId); var processedChunk = processReferenceChunk(request, task.id, ref); - request.completedJSONChunks.push(processedChunk); + request.completedErrorChunks.push(processedChunk); } function flushCompletedChunks(request, destination) { diff --git a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js index 95a6f04e34bb6ab..892fa9bace9be57 100644 --- a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js +++ b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js @@ -7,42 +7,44 @@ * 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 e=require("react"),l=null,m=0;function n(a,c){if(0!==c.length)if(512