Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update react-server-dom-webpack #40356

Merged
merged 2 commits into from Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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