Skip to content

Commit

Permalink
Update react-server-dom-webpack (#40356)
Browse files Browse the repository at this point in the history
Bump `react-server-dom-webpack` for support of `experimental_use` API
  • Loading branch information
huozhi committed Sep 8, 2022
1 parent ba4c575 commit c8d3fa6
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 70 deletions.
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -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",
Expand Down
Expand Up @@ -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) {
Expand Down Expand Up @@ -958,7 +1049,8 @@ var Dispatcher = {
},
useMemoCache: function (size) {
return new Array(size);
}
},
use: use
};

function unsupportedHook() {
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit c8d3fa6

Please sign in to comment.