mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
b2392647b5
In #28123 I switched these to be lazy references. However that creates a
lazy wrapper even if they're synchronously available. We try to as much
as possible preserve the original data structure in these cases.
E.g. here in the dev outlining I only use a lazy wrapper if it didn't
complete synchronously:
https://github.com/facebook/react/pull/28272/files#diff-d4c9c509922b3671d3ecce4e051df66dd5c3d38ff913c7a7fe94abc3ba2ed72eR638
Unfortunately we don't have a data structure that tracks the status of
each emitted row. We could store the task in the map but then they
couldn't be GC:ed as they complete. We could maybe store the status of
each element but seems so heavy.
For now I just went back to direct reference which might be an issue
since it can suspend something higher up when deduped.
DiffTrain build for [ba5e6a8329](https://github.com/facebook/react/commit/ba5e6a8329c7194a2c573c037a37f24ce45ee58f)
2605 lines
83 KiB
JavaScript
2605 lines
83 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @noflow
|
|
* @nolint
|
|
* @preventMunge
|
|
* @preserve-invariant-messages
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
if (__DEV__) {
|
|
(function () {
|
|
"use strict";
|
|
|
|
var ReactDOM = require("react-dom");
|
|
var React = require("react");
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
// eslint-disable-next-line no-unused-vars
|
|
var requestedClientReferencesKeys = new Set();
|
|
var checkIsClientReference;
|
|
function setCheckIsClientReference(impl) {
|
|
checkIsClientReference = impl;
|
|
}
|
|
function registerClientReference(clientReference) {}
|
|
function isClientReference(reference) {
|
|
if (checkIsClientReference == null) {
|
|
throw new Error("Expected implementation for checkIsClientReference.");
|
|
}
|
|
|
|
return checkIsClientReference(reference);
|
|
}
|
|
function getClientReferenceKey(clientReference) {
|
|
var moduleId = clientReference.getModuleId();
|
|
requestedClientReferencesKeys.add(moduleId);
|
|
return clientReference.getModuleId();
|
|
}
|
|
function resolveClientReferenceMetadata(config, clientReference) {
|
|
return {
|
|
moduleId: clientReference.getModuleId(),
|
|
exportName: "default"
|
|
};
|
|
}
|
|
function registerServerReference(serverReference, id, exportName) {
|
|
throw new Error("registerServerReference: Not Implemented.");
|
|
}
|
|
function isServerReference(reference) {
|
|
throw new Error("isServerReference: Not Implemented.");
|
|
}
|
|
function getServerReferenceId(config, serverReference) {
|
|
throw new Error("getServerReferenceId: Not Implemented.");
|
|
}
|
|
function getRequestedClientReferencesKeys() {
|
|
return Array.from(requestedClientReferencesKeys);
|
|
}
|
|
function clearRequestedClientReferencesKeysSet() {
|
|
requestedClientReferencesKeys.clear();
|
|
}
|
|
|
|
// This refers to a WWW module.
|
|
var warningWWW = require("warning");
|
|
function error(format) {
|
|
{
|
|
{
|
|
for (
|
|
var _len2 = arguments.length,
|
|
args = new Array(_len2 > 1 ? _len2 - 1 : 0),
|
|
_key2 = 1;
|
|
_key2 < _len2;
|
|
_key2++
|
|
) {
|
|
args[_key2 - 1] = arguments[_key2];
|
|
}
|
|
|
|
printWarning("error", format, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
function printWarning(level, format, args) {
|
|
{
|
|
var React = require("react");
|
|
|
|
var ReactSharedInternals =
|
|
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; // Defensive in case this is fired before React is initialized.
|
|
|
|
if (ReactSharedInternals != null) {
|
|
var ReactDebugCurrentFrame =
|
|
ReactSharedInternals.ReactDebugCurrentFrame;
|
|
var stack = ReactDebugCurrentFrame.getStackAddendum();
|
|
|
|
if (stack !== "") {
|
|
format += "%s";
|
|
args.push(stack);
|
|
}
|
|
} // TODO: don't ignore level and pass it down somewhere too.
|
|
|
|
args.unshift(format);
|
|
args.unshift(false);
|
|
warningWWW.apply(null, args);
|
|
}
|
|
}
|
|
|
|
// Re-export dynamic flags from the www version.
|
|
require("ReactFeatureFlags");
|
|
|
|
function stringToChunk(content) {
|
|
return content;
|
|
}
|
|
|
|
var byteLengthImpl = null;
|
|
function setByteLengthOfChunkImplementation(impl) {
|
|
byteLengthImpl = impl;
|
|
}
|
|
function byteLengthOfChunk(chunk) {
|
|
if (byteLengthImpl == null) {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw new Error(
|
|
"byteLengthOfChunk implementation is not configured. Please, provide the implementation via ReactFlightDOMServer.setConfig(...);"
|
|
);
|
|
}
|
|
|
|
return byteLengthImpl(chunk);
|
|
}
|
|
function scheduleWork(callback) {
|
|
callback();
|
|
}
|
|
function beginWriting(destination) {
|
|
destination.beginWriting();
|
|
}
|
|
function writeChunkAndReturn(destination, chunk) {
|
|
destination.write(chunk);
|
|
return true;
|
|
}
|
|
function completeWriting(destination) {
|
|
destination.completeWriting();
|
|
}
|
|
function flushBuffered(destination) {
|
|
destination.flushBuffered();
|
|
}
|
|
function close(destination) {
|
|
destination.close();
|
|
}
|
|
function closeWithError(destination, error) {
|
|
destination.onError(error);
|
|
destination.close();
|
|
}
|
|
|
|
function getServerReferenceBoundArguments(config, serverReference) {
|
|
throw new Error("getServerReferenceBoundArguments: Not Implemented.");
|
|
}
|
|
|
|
var ReactDOMSharedInternals =
|
|
ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
|
|
var ReactDOMFlightServerDispatcher = {
|
|
prefetchDNS: prefetchDNS,
|
|
preconnect: preconnect,
|
|
preload: preload,
|
|
preloadModule: preloadModule,
|
|
preinitStyle: preinitStyle,
|
|
preinitScript: preinitScript,
|
|
preinitModuleScript: preinitModuleScript
|
|
};
|
|
|
|
function prefetchDNS(href) {
|
|
{
|
|
if (typeof href === "string" && href) {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key = "D|" + href;
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
emitHint(request, "D", href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preconnect(href, crossOrigin) {
|
|
{
|
|
if (typeof href === "string") {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key =
|
|
"C|" + (crossOrigin == null ? "null" : crossOrigin) + "|" + href;
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
|
|
if (typeof crossOrigin === "string") {
|
|
emitHint(request, "C", [href, crossOrigin]);
|
|
} else {
|
|
emitHint(request, "C", href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preload(href, as, options) {
|
|
{
|
|
if (typeof href === "string") {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key = "L";
|
|
|
|
if (as === "image" && options) {
|
|
key += getImagePreloadKey(
|
|
href,
|
|
options.imageSrcSet,
|
|
options.imageSizes
|
|
);
|
|
} else {
|
|
key += "[" + as + "]" + href;
|
|
}
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
var trimmed = trimOptions(options);
|
|
|
|
if (trimmed) {
|
|
emitHint(request, "L", [href, as, trimmed]);
|
|
} else {
|
|
emitHint(request, "L", [href, as]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preloadModule(href, options) {
|
|
{
|
|
if (typeof href === "string") {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key = "m|" + href;
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
var trimmed = trimOptions(options);
|
|
|
|
if (trimmed) {
|
|
return emitHint(request, "m", [href, trimmed]);
|
|
} else {
|
|
return emitHint(request, "m", href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preinitStyle(href, precedence, options) {
|
|
{
|
|
if (typeof href === "string") {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key = "S|" + href;
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
var trimmed = trimOptions(options);
|
|
|
|
if (trimmed) {
|
|
return emitHint(request, "S", [
|
|
href,
|
|
typeof precedence === "string" ? precedence : 0,
|
|
trimmed
|
|
]);
|
|
} else if (typeof precedence === "string") {
|
|
return emitHint(request, "S", [href, precedence]);
|
|
} else {
|
|
return emitHint(request, "S", href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preinitScript(href, options) {
|
|
{
|
|
if (typeof href === "string") {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key = "X|" + href;
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
var trimmed = trimOptions(options);
|
|
|
|
if (trimmed) {
|
|
return emitHint(request, "X", [href, trimmed]);
|
|
} else {
|
|
return emitHint(request, "X", href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function preinitModuleScript(href, options) {
|
|
{
|
|
if (typeof href === "string") {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
var hints = getHints(request);
|
|
var key = "M|" + href;
|
|
|
|
if (hints.has(key)) {
|
|
// duplicate hint
|
|
return;
|
|
}
|
|
|
|
hints.add(key);
|
|
var trimmed = trimOptions(options);
|
|
|
|
if (trimmed) {
|
|
return emitHint(request, "M", [href, trimmed]);
|
|
} else {
|
|
return emitHint(request, "M", href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // Flight normally encodes undefined as a special character however for directive option
|
|
// arguments we don't want to send unnecessary keys and bloat the payload so we create a
|
|
// trimmed object which omits any keys with null or undefined values.
|
|
// This is only typesafe because these option objects have entirely optional fields where
|
|
// null and undefined represent the same thing as no property.
|
|
|
|
function trimOptions(options) {
|
|
if (options == null) return null;
|
|
var hasProperties = false;
|
|
var trimmed = {};
|
|
|
|
for (var key in options) {
|
|
if (options[key] != null) {
|
|
hasProperties = true;
|
|
trimmed[key] = options[key];
|
|
}
|
|
}
|
|
|
|
return hasProperties ? trimmed : null;
|
|
}
|
|
|
|
function getImagePreloadKey(href, imageSrcSet, imageSizes) {
|
|
var uniquePart = "";
|
|
|
|
if (typeof imageSrcSet === "string" && imageSrcSet !== "") {
|
|
uniquePart += "[" + imageSrcSet + "]";
|
|
|
|
if (typeof imageSizes === "string") {
|
|
uniquePart += "[" + imageSizes + "]";
|
|
}
|
|
} else {
|
|
uniquePart += "[][]" + href;
|
|
}
|
|
|
|
return "[image]" + uniquePart;
|
|
}
|
|
|
|
var ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
|
|
function prepareHostDispatcher() {
|
|
ReactDOMCurrentDispatcher.current = ReactDOMFlightServerDispatcher;
|
|
} // Used to distinguish these contexts from ones used in other renderers.
|
|
// small, smaller than how we encode undefined, and is unambiguous. We could use
|
|
// a different tuple structure to encode this instead but this makes the runtime
|
|
// cost cheaper by eliminating a type checks in more positions.
|
|
// prettier-ignore
|
|
|
|
function createHints() {
|
|
return new Set();
|
|
}
|
|
|
|
// ATTENTION
|
|
// When adding new symbols to this file,
|
|
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
|
|
// The Symbol used to tag the ReactElement-like types.
|
|
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
|
|
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
|
|
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
|
|
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
|
|
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
|
|
var REACT_MEMO_TYPE = Symbol.for("react.memo");
|
|
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
|
|
var REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
|
|
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
|
|
var FAUX_ITERATOR_SYMBOL = "@@iterator";
|
|
function getIteratorFn(maybeIterable) {
|
|
if (maybeIterable === null || typeof maybeIterable !== "object") {
|
|
return null;
|
|
}
|
|
|
|
var maybeIterator =
|
|
(MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL]) ||
|
|
maybeIterable[FAUX_ITERATOR_SYMBOL];
|
|
|
|
if (typeof maybeIterator === "function") {
|
|
return maybeIterator;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Corresponds to ReactFiberWakeable and ReactFizzWakeable modules. Generally,
|
|
// changes to one module should be reflected in the others.
|
|
// TODO: Rename this module and the corresponding Fiber one to "Thenable"
|
|
// instead of "Wakeable". Or some other more appropriate name.
|
|
// An error that is thrown (e.g. by `use`) to trigger Suspense. If we
|
|
// detect this is caught by userspace, we'll log a warning in development.
|
|
var SuspenseException = new Error(
|
|
"Suspense Exception: This is not a real error! It's an implementation " +
|
|
"detail of `use` to interrupt the current render. You must either " +
|
|
"rethrow it immediately, or move the `use` call outside of the " +
|
|
"`try/catch` block. Capturing without rethrowing will lead to " +
|
|
"unexpected behavior.\n\n" +
|
|
"To handle async errors, wrap your component in an error boundary, or " +
|
|
"call the promise's `.catch` method and pass the result to `use`"
|
|
);
|
|
function createThenableState() {
|
|
// The ThenableState is created the first time a component suspends. If it
|
|
// suspends again, we'll reuse the same state.
|
|
return [];
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
function trackUsedThenable(thenableState, thenable, index) {
|
|
var previous = thenableState[index];
|
|
|
|
if (previous === undefined) {
|
|
thenableState.push(thenable);
|
|
} else {
|
|
if (previous !== thenable) {
|
|
// Reuse the previous thenable, and drop the new one. We can assume
|
|
// they represent the same value, because components are idempotent.
|
|
// Avoid an unhandled rejection errors for the Promises that we'll
|
|
// intentionally ignore.
|
|
thenable.then(noop, noop);
|
|
thenable = previous;
|
|
}
|
|
} // 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 "fulfilled": {
|
|
var fulfilledValue = thenable.value;
|
|
return fulfilledValue;
|
|
}
|
|
|
|
case "rejected": {
|
|
var rejectedError = thenable.reason;
|
|
throw rejectedError;
|
|
}
|
|
|
|
default: {
|
|
if (typeof thenable.status === "string");
|
|
else {
|
|
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;
|
|
}
|
|
}
|
|
); // Check one more time in case the thenable resolved synchronously
|
|
|
|
switch (thenable.status) {
|
|
case "fulfilled": {
|
|
var fulfilledThenable = thenable;
|
|
return fulfilledThenable.value;
|
|
}
|
|
|
|
case "rejected": {
|
|
var rejectedThenable = thenable;
|
|
throw rejectedThenable.reason;
|
|
}
|
|
}
|
|
} // Suspend.
|
|
//
|
|
// 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.
|
|
|
|
suspendedThenable = thenable;
|
|
throw SuspenseException;
|
|
}
|
|
}
|
|
} // This is used to track the actual thenable that suspended so it can be
|
|
// passed to the rest of the Suspense implementation — which, for historical
|
|
// reasons, expects to receive a thenable.
|
|
|
|
var suspendedThenable = null;
|
|
function getSuspendedThenable() {
|
|
// This is called right after `use` suspends by throwing an exception. `use`
|
|
// throws an opaque value instead of the thenable itself so that it can't be
|
|
// caught in userspace. Then the work loop accesses the actual thenable using
|
|
// this function.
|
|
if (suspendedThenable === null) {
|
|
throw new Error(
|
|
"Expected a suspended thenable. This is a bug in React. Please file " +
|
|
"an issue."
|
|
);
|
|
}
|
|
|
|
var thenable = suspendedThenable;
|
|
suspendedThenable = null;
|
|
return thenable;
|
|
}
|
|
|
|
var currentRequest$1 = null;
|
|
var thenableIndexCounter = 0;
|
|
var thenableState = null;
|
|
function prepareToUseHooksForRequest(request) {
|
|
currentRequest$1 = request;
|
|
}
|
|
function resetHooksForRequest() {
|
|
currentRequest$1 = null;
|
|
}
|
|
function prepareToUseHooksForComponent(prevThenableState) {
|
|
thenableIndexCounter = 0;
|
|
thenableState = prevThenableState;
|
|
}
|
|
function getThenableStateAfterSuspending() {
|
|
// If you use() to Suspend this should always exist but if you throw a Promise instead,
|
|
// which is not really supported anymore, it will be empty. We use the empty set as a
|
|
// marker to know if this was a replay of the same component or first attempt.
|
|
var state = thenableState || createThenableState();
|
|
thenableState = null;
|
|
return state;
|
|
}
|
|
var HooksDispatcher = {
|
|
useMemo: function (nextCreate) {
|
|
return nextCreate();
|
|
},
|
|
useCallback: function (callback) {
|
|
return callback;
|
|
},
|
|
useDebugValue: function () {},
|
|
useDeferredValue: unsupportedHook,
|
|
useTransition: unsupportedHook,
|
|
readContext: unsupportedContext,
|
|
useContext: unsupportedContext,
|
|
useReducer: unsupportedHook,
|
|
useRef: unsupportedHook,
|
|
useState: unsupportedHook,
|
|
useInsertionEffect: unsupportedHook,
|
|
useLayoutEffect: unsupportedHook,
|
|
useImperativeHandle: unsupportedHook,
|
|
useEffect: unsupportedHook,
|
|
useId: useId,
|
|
useSyncExternalStore: unsupportedHook,
|
|
useCacheRefresh: function () {
|
|
return unsupportedRefresh;
|
|
},
|
|
useMemoCache: function (size) {
|
|
var data = new Array(size);
|
|
|
|
for (var i = 0; i < size; i++) {
|
|
data[i] = REACT_MEMO_CACHE_SENTINEL;
|
|
}
|
|
|
|
return data;
|
|
},
|
|
use: use
|
|
};
|
|
|
|
function unsupportedHook() {
|
|
throw new Error("This Hook is not supported in Server Components.");
|
|
}
|
|
|
|
function unsupportedRefresh() {
|
|
throw new Error(
|
|
"Refreshing the cache is not supported in Server Components."
|
|
);
|
|
}
|
|
|
|
function unsupportedContext() {
|
|
throw new Error("Cannot read a Client Context from a Server Component.");
|
|
}
|
|
|
|
function useId() {
|
|
if (currentRequest$1 === null) {
|
|
throw new Error("useId can only be used while React is rendering");
|
|
}
|
|
|
|
var id = currentRequest$1.identifierCount++; // use 'S' for Flight components to distinguish from 'R' and 'r' in Fizz/Client
|
|
|
|
return (
|
|
":" + currentRequest$1.identifierPrefix + "S" + id.toString(32) + ":"
|
|
);
|
|
}
|
|
|
|
function use(usable) {
|
|
if (
|
|
(usable !== null && typeof usable === "object") ||
|
|
typeof usable === "function"
|
|
) {
|
|
// $FlowFixMe[method-unbinding]
|
|
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;
|
|
|
|
if (thenableState === null) {
|
|
thenableState = createThenableState();
|
|
}
|
|
|
|
return trackUsedThenable(thenableState, thenable, index);
|
|
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
|
|
unsupportedContext();
|
|
}
|
|
}
|
|
|
|
if (isClientReference(usable)) {
|
|
if (
|
|
usable.value != null &&
|
|
usable.value.$$typeof === REACT_CONTEXT_TYPE
|
|
) {
|
|
// Show a more specific message since it's a common mistake.
|
|
throw new Error(
|
|
"Cannot read a Client Context from a Server Component."
|
|
);
|
|
} else {
|
|
throw new Error("Cannot use() an already resolved Client Reference.");
|
|
}
|
|
} else {
|
|
throw new Error( // eslint-disable-next-line react-internal/safe-string-coercion
|
|
"An unsupported type was passed to use(): " + String(usable)
|
|
);
|
|
}
|
|
}
|
|
|
|
function createSignal() {
|
|
return new AbortController().signal;
|
|
}
|
|
|
|
function resolveCache() {
|
|
var request = resolveRequest();
|
|
|
|
if (request) {
|
|
return getCache(request);
|
|
}
|
|
|
|
return new Map();
|
|
}
|
|
|
|
var DefaultCacheDispatcher = {
|
|
getCacheSignal: function () {
|
|
var cache = resolveCache();
|
|
var entry = cache.get(createSignal);
|
|
|
|
if (entry === undefined) {
|
|
entry = createSignal();
|
|
cache.set(createSignal, entry);
|
|
}
|
|
|
|
return entry;
|
|
},
|
|
getCacheForType: function (resourceType) {
|
|
var cache = resolveCache();
|
|
var entry = cache.get(resourceType);
|
|
|
|
if (entry === undefined) {
|
|
entry = resourceType(); // TODO: Warn if undefined?
|
|
|
|
cache.set(resourceType, entry);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
};
|
|
|
|
var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare
|
|
|
|
function isArray(a) {
|
|
return isArrayImpl(a);
|
|
}
|
|
|
|
var getPrototypeOf = Object.getPrototypeOf;
|
|
|
|
// in case they error.
|
|
|
|
var jsxPropsParents = new WeakMap();
|
|
var jsxChildrenParents = new WeakMap();
|
|
|
|
function isObjectPrototype(object) {
|
|
if (!object) {
|
|
return false;
|
|
}
|
|
|
|
var ObjectPrototype = Object.prototype;
|
|
|
|
if (object === ObjectPrototype) {
|
|
return true;
|
|
} // It might be an object from a different Realm which is
|
|
// still just a plain simple object.
|
|
|
|
if (getPrototypeOf(object)) {
|
|
return false;
|
|
}
|
|
|
|
var names = Object.getOwnPropertyNames(object);
|
|
|
|
for (var i = 0; i < names.length; i++) {
|
|
if (!(names[i] in ObjectPrototype)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function isSimpleObject(object) {
|
|
if (!isObjectPrototype(getPrototypeOf(object))) {
|
|
return false;
|
|
}
|
|
|
|
var names = Object.getOwnPropertyNames(object);
|
|
|
|
for (var i = 0; i < names.length; i++) {
|
|
var descriptor = Object.getOwnPropertyDescriptor(object, names[i]);
|
|
|
|
if (!descriptor) {
|
|
return false;
|
|
}
|
|
|
|
if (!descriptor.enumerable) {
|
|
if (
|
|
(names[i] === "key" || names[i] === "ref") &&
|
|
typeof descriptor.get === "function"
|
|
) {
|
|
// React adds key and ref getters to props objects to issue warnings.
|
|
// Those getters will not be transferred to the client, but that's ok,
|
|
// so we'll special case them.
|
|
continue;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
function objectName(object) {
|
|
// $FlowFixMe[method-unbinding]
|
|
var name = Object.prototype.toString.call(object);
|
|
return name.replace(/^\[object (.*)\]$/, function (m, p0) {
|
|
return p0;
|
|
});
|
|
}
|
|
|
|
function describeKeyForErrorMessage(key) {
|
|
var encodedKey = JSON.stringify(key);
|
|
return '"' + key + '"' === encodedKey ? key : encodedKey;
|
|
}
|
|
|
|
function describeValueForErrorMessage(value) {
|
|
switch (typeof value) {
|
|
case "string": {
|
|
return JSON.stringify(
|
|
value.length <= 10 ? value : value.slice(0, 10) + "..."
|
|
);
|
|
}
|
|
|
|
case "object": {
|
|
if (isArray(value)) {
|
|
return "[...]";
|
|
}
|
|
|
|
var name = objectName(value);
|
|
|
|
if (name === "Object") {
|
|
return "{...}";
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
case "function":
|
|
return "function";
|
|
|
|
default:
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
function describeElementType(type) {
|
|
if (typeof type === "string") {
|
|
return type;
|
|
}
|
|
|
|
switch (type) {
|
|
case REACT_SUSPENSE_TYPE:
|
|
return "Suspense";
|
|
|
|
case REACT_SUSPENSE_LIST_TYPE:
|
|
return "SuspenseList";
|
|
}
|
|
|
|
if (typeof type === "object") {
|
|
switch (type.$$typeof) {
|
|
case REACT_FORWARD_REF_TYPE:
|
|
return describeElementType(type.render);
|
|
|
|
case REACT_MEMO_TYPE:
|
|
return describeElementType(type.type);
|
|
|
|
case REACT_LAZY_TYPE: {
|
|
var lazyComponent = type;
|
|
var payload = lazyComponent._payload;
|
|
var init = lazyComponent._init;
|
|
|
|
try {
|
|
// Lazy may contain any component type so we recursively resolve it.
|
|
return describeElementType(init(payload));
|
|
} catch (x) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
function describeObjectForErrorMessage(objectOrArray, expandedName) {
|
|
var objKind = objectName(objectOrArray);
|
|
|
|
if (objKind !== "Object" && objKind !== "Array") {
|
|
return objKind;
|
|
}
|
|
|
|
var str = "";
|
|
var start = -1;
|
|
var length = 0;
|
|
|
|
if (isArray(objectOrArray)) {
|
|
if (jsxChildrenParents.has(objectOrArray)) {
|
|
// Print JSX Children
|
|
var type = jsxChildrenParents.get(objectOrArray);
|
|
str = "<" + describeElementType(type) + ">";
|
|
var array = objectOrArray;
|
|
|
|
for (var i = 0; i < array.length; i++) {
|
|
var value = array[i];
|
|
var substr = void 0;
|
|
|
|
if (typeof value === "string") {
|
|
substr = value;
|
|
} else if (typeof value === "object" && value !== null) {
|
|
substr = "{" + describeObjectForErrorMessage(value) + "}";
|
|
} else {
|
|
substr = "{" + describeValueForErrorMessage(value) + "}";
|
|
}
|
|
|
|
if ("" + i === expandedName) {
|
|
start = str.length;
|
|
length = substr.length;
|
|
str += substr;
|
|
} else if (substr.length < 15 && str.length + substr.length < 40) {
|
|
str += substr;
|
|
} else {
|
|
str += "{...}";
|
|
}
|
|
}
|
|
|
|
str += "</" + describeElementType(type) + ">";
|
|
} else {
|
|
// Print Array
|
|
str = "[";
|
|
var _array = objectOrArray;
|
|
|
|
for (var _i = 0; _i < _array.length; _i++) {
|
|
if (_i > 0) {
|
|
str += ", ";
|
|
}
|
|
|
|
var _value = _array[_i];
|
|
|
|
var _substr = void 0;
|
|
|
|
if (typeof _value === "object" && _value !== null) {
|
|
_substr = describeObjectForErrorMessage(_value);
|
|
} else {
|
|
_substr = describeValueForErrorMessage(_value);
|
|
}
|
|
|
|
if ("" + _i === expandedName) {
|
|
start = str.length;
|
|
length = _substr.length;
|
|
str += _substr;
|
|
} else if (
|
|
_substr.length < 10 &&
|
|
str.length + _substr.length < 40
|
|
) {
|
|
str += _substr;
|
|
} else {
|
|
str += "...";
|
|
}
|
|
}
|
|
|
|
str += "]";
|
|
}
|
|
} else {
|
|
if (objectOrArray.$$typeof === REACT_ELEMENT_TYPE) {
|
|
str = "<" + describeElementType(objectOrArray.type) + "/>";
|
|
} else if (jsxPropsParents.has(objectOrArray)) {
|
|
// Print JSX
|
|
var _type = jsxPropsParents.get(objectOrArray);
|
|
|
|
str = "<" + (describeElementType(_type) || "...");
|
|
var object = objectOrArray;
|
|
var names = Object.keys(object);
|
|
|
|
for (var _i2 = 0; _i2 < names.length; _i2++) {
|
|
str += " ";
|
|
var name = names[_i2];
|
|
str += describeKeyForErrorMessage(name) + "=";
|
|
var _value2 = object[name];
|
|
|
|
var _substr2 = void 0;
|
|
|
|
if (
|
|
name === expandedName &&
|
|
typeof _value2 === "object" &&
|
|
_value2 !== null
|
|
) {
|
|
_substr2 = describeObjectForErrorMessage(_value2);
|
|
} else {
|
|
_substr2 = describeValueForErrorMessage(_value2);
|
|
}
|
|
|
|
if (typeof _value2 !== "string") {
|
|
_substr2 = "{" + _substr2 + "}";
|
|
}
|
|
|
|
if (name === expandedName) {
|
|
start = str.length;
|
|
length = _substr2.length;
|
|
str += _substr2;
|
|
} else if (
|
|
_substr2.length < 10 &&
|
|
str.length + _substr2.length < 40
|
|
) {
|
|
str += _substr2;
|
|
} else {
|
|
str += "...";
|
|
}
|
|
}
|
|
|
|
str += ">";
|
|
} else {
|
|
// Print Object
|
|
str = "{";
|
|
var _object = objectOrArray;
|
|
|
|
var _names = Object.keys(_object);
|
|
|
|
for (var _i3 = 0; _i3 < _names.length; _i3++) {
|
|
if (_i3 > 0) {
|
|
str += ", ";
|
|
}
|
|
|
|
var _name = _names[_i3];
|
|
str += describeKeyForErrorMessage(_name) + ": ";
|
|
var _value3 = _object[_name];
|
|
|
|
var _substr3 = void 0;
|
|
|
|
if (typeof _value3 === "object" && _value3 !== null) {
|
|
_substr3 = describeObjectForErrorMessage(_value3);
|
|
} else {
|
|
_substr3 = describeValueForErrorMessage(_value3);
|
|
}
|
|
|
|
if (_name === expandedName) {
|
|
start = str.length;
|
|
length = _substr3.length;
|
|
str += _substr3;
|
|
} else if (
|
|
_substr3.length < 10 &&
|
|
str.length + _substr3.length < 40
|
|
) {
|
|
str += _substr3;
|
|
} else {
|
|
str += "...";
|
|
}
|
|
}
|
|
|
|
str += "}";
|
|
}
|
|
}
|
|
|
|
if (expandedName === undefined) {
|
|
return str;
|
|
}
|
|
|
|
if (start > -1 && length > 0) {
|
|
var highlight = " ".repeat(start) + "^".repeat(length);
|
|
return "\n " + str + "\n " + highlight;
|
|
}
|
|
|
|
return "\n " + str;
|
|
}
|
|
|
|
var ReactSharedInternals =
|
|
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
|
|
var ReactSharedServerInternals = // $FlowFixMe: It's defined in the one we resolve to.
|
|
React.__SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
|
|
if (!ReactSharedServerInternals) {
|
|
throw new Error(
|
|
'The "react" package in this environment is not configured correctly. ' +
|
|
'The "react-server" condition must be enabled in any environment that ' +
|
|
"runs React Server Components."
|
|
);
|
|
}
|
|
|
|
var ObjectPrototype = Object.prototype;
|
|
var stringify = JSON.stringify; // Serializable values
|
|
// Thenable<ReactClientValue>
|
|
|
|
var PENDING = 0;
|
|
var COMPLETED = 1;
|
|
var ERRORED = 4;
|
|
var ReactCurrentCache = ReactSharedServerInternals.ReactCurrentCache;
|
|
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
|
|
|
function defaultErrorHandler(error) {
|
|
console["error"](error); // Don't transform to our wrapper
|
|
}
|
|
|
|
function defaultPostponeHandler(reason) {
|
|
// Noop
|
|
}
|
|
|
|
var OPEN = 0;
|
|
var CLOSING = 1;
|
|
var CLOSED = 2;
|
|
function createRequest(
|
|
model,
|
|
bundlerConfig,
|
|
onError,
|
|
identifierPrefix,
|
|
onPostpone
|
|
) {
|
|
if (
|
|
ReactCurrentCache.current !== null &&
|
|
ReactCurrentCache.current !== DefaultCacheDispatcher
|
|
) {
|
|
throw new Error(
|
|
"Currently React only supports one RSC renderer at a time."
|
|
);
|
|
}
|
|
|
|
prepareHostDispatcher();
|
|
ReactCurrentCache.current = DefaultCacheDispatcher;
|
|
var abortSet = new Set();
|
|
var pingedTasks = [];
|
|
var cleanupQueue = [];
|
|
|
|
var hints = createHints();
|
|
var request = {
|
|
status: OPEN,
|
|
flushScheduled: false,
|
|
fatalError: null,
|
|
destination: null,
|
|
bundlerConfig: bundlerConfig,
|
|
cache: new Map(),
|
|
nextChunkId: 0,
|
|
pendingChunks: 0,
|
|
hints: hints,
|
|
abortableTasks: abortSet,
|
|
pingedTasks: pingedTasks,
|
|
completedImportChunks: [],
|
|
completedHintChunks: [],
|
|
completedRegularChunks: [],
|
|
completedErrorChunks: [],
|
|
writtenSymbols: new Map(),
|
|
writtenClientReferences: new Map(),
|
|
writtenServerReferences: new Map(),
|
|
writtenObjects: new WeakMap(),
|
|
identifierPrefix: identifierPrefix || "",
|
|
identifierCount: 1,
|
|
taintCleanupQueue: cleanupQueue,
|
|
onError: onError === undefined ? defaultErrorHandler : onError,
|
|
onPostpone:
|
|
onPostpone === undefined ? defaultPostponeHandler : onPostpone
|
|
};
|
|
var rootTask = createTask(request, model, null, false, abortSet);
|
|
pingedTasks.push(rootTask);
|
|
return request;
|
|
}
|
|
var currentRequest = null;
|
|
function resolveRequest() {
|
|
if (currentRequest) return currentRequest;
|
|
|
|
return null;
|
|
}
|
|
|
|
function serializeThenable(request, task, thenable) {
|
|
var newTask = createTask(
|
|
request,
|
|
null,
|
|
task.keyPath, // the server component sequence continues through Promise-as-a-child.
|
|
task.implicitSlot,
|
|
request.abortableTasks
|
|
);
|
|
|
|
switch (thenable.status) {
|
|
case "fulfilled": {
|
|
// We have the resolved value, we can go ahead and schedule it for serialization.
|
|
newTask.model = thenable.value;
|
|
pingTask(request, newTask);
|
|
return newTask.id;
|
|
}
|
|
|
|
case "rejected": {
|
|
var x = thenable.reason;
|
|
|
|
{
|
|
var digest = logRecoverableError(request, x);
|
|
emitErrorChunk(request, newTask.id, digest, x);
|
|
}
|
|
|
|
return newTask.id;
|
|
}
|
|
|
|
default: {
|
|
if (typeof thenable.status === "string") {
|
|
// 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. We treat it as "pending".
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
thenable.then(
|
|
function (value) {
|
|
newTask.model = value;
|
|
pingTask(request, newTask);
|
|
},
|
|
function (reason) {
|
|
{
|
|
newTask.status = ERRORED;
|
|
|
|
var _digest = logRecoverableError(request, reason);
|
|
|
|
emitErrorChunk(request, newTask.id, _digest, reason);
|
|
}
|
|
|
|
request.abortableTasks.delete(newTask);
|
|
|
|
if (request.destination !== null) {
|
|
flushCompletedChunks(request, request.destination);
|
|
}
|
|
}
|
|
);
|
|
return newTask.id;
|
|
}
|
|
|
|
function emitHint(request, code, model) {
|
|
emitHintChunk(request, code, model);
|
|
enqueueFlush(request);
|
|
}
|
|
function getHints(request) {
|
|
return request.hints;
|
|
}
|
|
function getCache(request) {
|
|
return request.cache;
|
|
}
|
|
|
|
function readThenable(thenable) {
|
|
if (thenable.status === "fulfilled") {
|
|
return thenable.value;
|
|
} else if (thenable.status === "rejected") {
|
|
throw thenable.reason;
|
|
}
|
|
|
|
throw thenable;
|
|
}
|
|
|
|
function createLazyWrapperAroundWakeable(wakeable) {
|
|
// This is a temporary fork of the `use` implementation until we accept
|
|
// promises everywhere.
|
|
var thenable = wakeable;
|
|
|
|
switch (thenable.status) {
|
|
case "fulfilled":
|
|
case "rejected":
|
|
break;
|
|
|
|
default: {
|
|
if (typeof thenable.status === "string") {
|
|
// 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. We treat it as "pending".
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
var lazyType = {
|
|
$$typeof: REACT_LAZY_TYPE,
|
|
_payload: thenable,
|
|
_init: readThenable
|
|
};
|
|
return lazyType;
|
|
}
|
|
|
|
function renderFunctionComponent(request, task, key, Component, props) {
|
|
// Reset the task's thenable state before continuing, so that if a later
|
|
// component suspends we can reuse the same task object. If the same
|
|
// component suspends again, the thenable state will be restored.
|
|
var prevThenableState = task.thenableState;
|
|
task.thenableState = null;
|
|
|
|
{
|
|
if (debugID === null) {
|
|
// We don't have a chunk to assign debug info. We need to outline this
|
|
// component to assign it an ID.
|
|
return outlineTask(request, task);
|
|
} else if (prevThenableState !== null);
|
|
else {
|
|
// This is a new component in the same task so we can emit more debug info.
|
|
var componentName = Component.displayName || Component.name || "";
|
|
request.pendingChunks++;
|
|
emitDebugChunk(request, debugID, {
|
|
name: componentName
|
|
});
|
|
}
|
|
}
|
|
|
|
prepareToUseHooksForComponent(prevThenableState); // The secondArg is always undefined in Server Components since refs error early.
|
|
|
|
var secondArg = undefined;
|
|
var result = Component(props, secondArg);
|
|
|
|
if (
|
|
typeof result === "object" &&
|
|
result !== null &&
|
|
typeof result.then === "function"
|
|
) {
|
|
// When the return value is in children position we can resolve it immediately,
|
|
// to its value without a wrapper if it's synchronously available.
|
|
var thenable = result;
|
|
|
|
if (thenable.status === "fulfilled") {
|
|
return thenable.value;
|
|
} // TODO: Once we accept Promises as children on the client, we can just return
|
|
// the thenable here.
|
|
|
|
result = createLazyWrapperAroundWakeable(result);
|
|
} // Track this element's key on the Server Component on the keyPath context..
|
|
|
|
var prevKeyPath = task.keyPath;
|
|
var prevImplicitSlot = task.implicitSlot;
|
|
|
|
if (key !== null) {
|
|
// Append the key to the path. Technically a null key should really add the child
|
|
// index. We don't do that to hold the payload small and implementation simple.
|
|
task.keyPath = prevKeyPath === null ? key : prevKeyPath + "," + key;
|
|
} else if (prevKeyPath === null) {
|
|
// This sequence of Server Components has no keys. This means that it was rendered
|
|
// in a slot that needs to assign an implicit key. Even if children below have
|
|
// explicit keys, they should not be used for the outer most key since it might
|
|
// collide with other slots in that set.
|
|
task.implicitSlot = true;
|
|
}
|
|
|
|
var json = renderModelDestructive(request, task, emptyRoot, "", result);
|
|
task.keyPath = prevKeyPath;
|
|
task.implicitSlot = prevImplicitSlot;
|
|
return json;
|
|
}
|
|
|
|
function renderFragment(request, task, children) {
|
|
if (task.keyPath !== null) {
|
|
// We have a Server Component that specifies a key but we're now splitting
|
|
// the tree using a fragment.
|
|
var fragment = [
|
|
REACT_ELEMENT_TYPE,
|
|
REACT_FRAGMENT_TYPE,
|
|
task.keyPath,
|
|
{
|
|
children: children
|
|
}
|
|
];
|
|
|
|
if (!task.implicitSlot) {
|
|
// If this was keyed inside a set. I.e. the outer Server Component was keyed
|
|
// then we need to handle reorders of the whole set. To do this we need to wrap
|
|
// this array in a keyed Fragment.
|
|
return fragment;
|
|
} // If the outer Server Component was implicit but then an inner one had a key
|
|
// we don't actually need to be able to move the whole set around. It'll always be
|
|
// in an implicit slot. The key only exists to be able to reset the state of the
|
|
// children. We could achieve the same effect by passing on the keyPath to the next
|
|
// set of components inside the fragment. This would also allow a keyless fragment
|
|
// reconcile against a single child.
|
|
// Unfortunately because of JSON.stringify, we can't call the recursive loop for
|
|
// each child within this context because we can't return a set with already resolved
|
|
// values. E.g. a string would get double encoded. Returning would pop the context.
|
|
// So instead, we wrap it with an unkeyed fragment and inner keyed fragment.
|
|
|
|
return [fragment];
|
|
} // Since we're yielding here, that implicitly resets the keyPath context on the
|
|
// way up. Which is what we want since we've consumed it. If this changes to
|
|
// be recursive serialization, we need to reset the keyPath and implicitSlot,
|
|
// before recursing here.
|
|
|
|
return children;
|
|
}
|
|
|
|
function renderClientElement(task, type, key, props) {
|
|
// the keys of any Server Components which are not serialized.
|
|
|
|
var keyPath = task.keyPath;
|
|
|
|
if (key === null) {
|
|
key = keyPath;
|
|
} else if (keyPath !== null) {
|
|
key = keyPath + "," + key;
|
|
}
|
|
|
|
var element = [REACT_ELEMENT_TYPE, type, key, props];
|
|
|
|
if (task.implicitSlot && key !== null) {
|
|
// The root Server Component had no key so it was in an implicit slot.
|
|
// If we had a key lower, it would end up in that slot with an explicit key.
|
|
// We wrap the element in a fragment to give it an implicit key slot with
|
|
// an inner explicit key.
|
|
return [element];
|
|
} // Since we're yielding here, that implicitly resets the keyPath context on the
|
|
// way up. Which is what we want since we've consumed it. If this changes to
|
|
// be recursive serialization, we need to reset the keyPath and implicitSlot,
|
|
// before recursing here. We also need to reset it once we render into an array
|
|
// or anything else too which we also get implicitly.
|
|
|
|
return element;
|
|
} // The chunk ID we're currently rendering that we can assign debug data to.
|
|
|
|
var debugID = null;
|
|
|
|
function outlineTask(request, task) {
|
|
var newTask = createTask(
|
|
request,
|
|
task.model, // the currently rendering element
|
|
task.keyPath, // unlike outlineModel this one carries along context
|
|
task.implicitSlot,
|
|
request.abortableTasks
|
|
);
|
|
retryTask(request, newTask);
|
|
|
|
if (newTask.status === COMPLETED) {
|
|
// We completed synchronously so we can refer to this by reference. This
|
|
// makes it behaves the same as prod during deserialization.
|
|
return serializeByValueID(newTask.id);
|
|
} // This didn't complete synchronously so it wouldn't have even if we didn't
|
|
// outline it, so this would reduce to a lazy reference even in prod.
|
|
|
|
return serializeLazyID(newTask.id);
|
|
}
|
|
|
|
function renderElement(request, task, type, key, ref, props) {
|
|
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
|
|
// cases.
|
|
throw new Error(
|
|
"Refs cannot be used in Server Components, nor passed to Client Components."
|
|
);
|
|
}
|
|
|
|
{
|
|
jsxPropsParents.set(props, type);
|
|
|
|
if (typeof props.children === "object" && props.children !== null) {
|
|
jsxChildrenParents.set(props.children, type);
|
|
}
|
|
}
|
|
|
|
if (typeof type === "function") {
|
|
if (isClientReference(type)) {
|
|
// This is a reference to a Client Component.
|
|
return renderClientElement(task, type, key, props);
|
|
} // This is a Server Component.
|
|
|
|
return renderFunctionComponent(request, task, key, type, props);
|
|
} else if (typeof type === "string") {
|
|
// This is a host element. E.g. HTML.
|
|
return renderClientElement(task, type, key, props);
|
|
} else if (typeof type === "symbol") {
|
|
if (type === REACT_FRAGMENT_TYPE && key === null) {
|
|
// For key-less fragments, we add a small optimization to avoid serializing
|
|
// it as a wrapper.
|
|
var prevImplicitSlot = task.implicitSlot;
|
|
|
|
if (task.keyPath === null) {
|
|
task.implicitSlot = true;
|
|
}
|
|
|
|
var json = renderModelDestructive(
|
|
request,
|
|
task,
|
|
emptyRoot,
|
|
"",
|
|
props.children
|
|
);
|
|
task.implicitSlot = prevImplicitSlot;
|
|
return json;
|
|
} // This might be a built-in React component. We'll let the client decide.
|
|
// Any built-in works as long as its props are serializable.
|
|
|
|
return renderClientElement(task, type, key, props);
|
|
} else if (type != null && typeof type === "object") {
|
|
if (isClientReference(type)) {
|
|
// This is a reference to a Client Component.
|
|
return renderClientElement(task, type, key, props);
|
|
}
|
|
|
|
switch (type.$$typeof) {
|
|
case REACT_LAZY_TYPE: {
|
|
var payload = type._payload;
|
|
var init = type._init;
|
|
var wrappedType = init(payload);
|
|
return renderElement(request, task, wrappedType, key, ref, props);
|
|
}
|
|
|
|
case REACT_FORWARD_REF_TYPE: {
|
|
return renderFunctionComponent(
|
|
request,
|
|
task,
|
|
key,
|
|
type.render,
|
|
props
|
|
);
|
|
}
|
|
|
|
case REACT_MEMO_TYPE: {
|
|
return renderElement(request, task, type.type, key, ref, props);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
"Unsupported Server Component type: " +
|
|
describeValueForErrorMessage(type)
|
|
);
|
|
}
|
|
|
|
function pingTask(request, task) {
|
|
var pingedTasks = request.pingedTasks;
|
|
pingedTasks.push(task);
|
|
|
|
if (pingedTasks.length === 1) {
|
|
request.flushScheduled = request.destination !== null;
|
|
scheduleWork(function () {
|
|
return performWork(request);
|
|
});
|
|
}
|
|
}
|
|
|
|
function createTask(request, model, keyPath, implicitSlot, abortSet) {
|
|
request.pendingChunks++;
|
|
var id = request.nextChunkId++;
|
|
|
|
if (typeof model === "object" && model !== null) {
|
|
// If we're about to write this into a new task we can assign it an ID early so that
|
|
// any other references can refer to the value we're about to write.
|
|
if (keyPath !== null || implicitSlot);
|
|
else {
|
|
request.writtenObjects.set(model, id);
|
|
}
|
|
}
|
|
|
|
var task = {
|
|
id: id,
|
|
status: PENDING,
|
|
model: model,
|
|
keyPath: keyPath,
|
|
implicitSlot: implicitSlot,
|
|
ping: function () {
|
|
return pingTask(request, task);
|
|
},
|
|
toJSON: function (parentPropertyName, value) {
|
|
var parent = this; // Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
|
|
|
|
{
|
|
// $FlowFixMe[incompatible-use]
|
|
var originalValue = parent[parentPropertyName];
|
|
|
|
if (
|
|
typeof originalValue === "object" &&
|
|
originalValue !== value &&
|
|
!(originalValue instanceof Date)
|
|
) {
|
|
if (objectName(originalValue) !== "Object") {
|
|
var jsxParentType = jsxChildrenParents.get(parent);
|
|
|
|
if (typeof jsxParentType === "string") {
|
|
error(
|
|
"%s objects cannot be rendered as text children. Try formatting it using toString().%s",
|
|
objectName(originalValue),
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
} else {
|
|
error(
|
|
"Only plain objects can be passed to Client Components from Server Components. " +
|
|
"%s objects are not supported.%s",
|
|
objectName(originalValue),
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
}
|
|
} else {
|
|
error(
|
|
"Only plain objects can be passed to Client Components from Server Components. " +
|
|
"Objects with toJSON methods are not supported. Convert it manually " +
|
|
"to a simple value before passing it to props.%s",
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return renderModel(request, task, parent, parentPropertyName, value);
|
|
},
|
|
thenableState: null
|
|
};
|
|
abortSet.add(task);
|
|
return task;
|
|
}
|
|
|
|
function serializeByValueID(id) {
|
|
return "$" + id.toString(16);
|
|
}
|
|
|
|
function serializeLazyID(id) {
|
|
return "$L" + id.toString(16);
|
|
}
|
|
|
|
function serializePromiseID(id) {
|
|
return "$@" + id.toString(16);
|
|
}
|
|
|
|
function serializeServerReferenceID(id) {
|
|
return "$F" + id.toString(16);
|
|
}
|
|
|
|
function serializeSymbolReference(name) {
|
|
return "$S" + name;
|
|
}
|
|
|
|
function serializeNumber(number) {
|
|
if (Number.isFinite(number)) {
|
|
if (number === 0 && 1 / number === -Infinity) {
|
|
return "$-0";
|
|
} else {
|
|
return number;
|
|
}
|
|
} else {
|
|
if (number === Infinity) {
|
|
return "$Infinity";
|
|
} else if (number === -Infinity) {
|
|
return "$-Infinity";
|
|
} else {
|
|
return "$NaN";
|
|
}
|
|
}
|
|
}
|
|
|
|
function serializeUndefined() {
|
|
return "$undefined";
|
|
}
|
|
|
|
function serializeDateFromDateJSON(dateJSON) {
|
|
// JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
|
|
// We need only tack on a $D prefix.
|
|
return "$D" + dateJSON;
|
|
}
|
|
|
|
function serializeBigInt(n) {
|
|
return "$n" + n.toString(10);
|
|
}
|
|
|
|
function serializeRowHeader(tag, id) {
|
|
return id.toString(16) + ":" + tag;
|
|
}
|
|
|
|
function encodeReferenceChunk(request, id, reference) {
|
|
var json = stringify(reference);
|
|
var row = id.toString(16) + ":" + json + "\n";
|
|
return stringToChunk(row);
|
|
}
|
|
|
|
function serializeClientReference(
|
|
request,
|
|
parent,
|
|
parentPropertyName,
|
|
clientReference
|
|
) {
|
|
var clientReferenceKey = getClientReferenceKey(clientReference);
|
|
var writtenClientReferences = request.writtenClientReferences;
|
|
var existingId = writtenClientReferences.get(clientReferenceKey);
|
|
|
|
if (existingId !== undefined) {
|
|
if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === "1") {
|
|
// If we're encoding the "type" of an element, we can refer
|
|
// to that by a lazy reference instead of directly since React
|
|
// knows how to deal with lazy values. This lets us suspend
|
|
// on this component rather than its parent until the code has
|
|
// loaded.
|
|
return serializeLazyID(existingId);
|
|
}
|
|
|
|
return serializeByValueID(existingId);
|
|
}
|
|
|
|
try {
|
|
var clientReferenceMetadata = resolveClientReferenceMetadata(
|
|
request.bundlerConfig,
|
|
clientReference
|
|
);
|
|
request.pendingChunks++;
|
|
var importId = request.nextChunkId++;
|
|
emitImportChunk(request, importId, clientReferenceMetadata);
|
|
writtenClientReferences.set(clientReferenceKey, importId);
|
|
|
|
if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === "1") {
|
|
// If we're encoding the "type" of an element, we can refer
|
|
// to that by a lazy reference instead of directly since React
|
|
// knows how to deal with lazy values. This lets us suspend
|
|
// on this component rather than its parent until the code has
|
|
// loaded.
|
|
return serializeLazyID(importId);
|
|
}
|
|
|
|
return serializeByValueID(importId);
|
|
} catch (x) {
|
|
request.pendingChunks++;
|
|
var errorId = request.nextChunkId++;
|
|
var digest = logRecoverableError(request, x);
|
|
emitErrorChunk(request, errorId, digest, x);
|
|
return serializeByValueID(errorId);
|
|
}
|
|
}
|
|
|
|
function outlineModel(request, value) {
|
|
var newTask = createTask(
|
|
request,
|
|
value,
|
|
null, // The way we use outlining is for reusing an object.
|
|
false, // It makes no sense for that use case to be contextual.
|
|
request.abortableTasks
|
|
);
|
|
retryTask(request, newTask);
|
|
return newTask.id;
|
|
}
|
|
|
|
function serializeServerReference(request, serverReference) {
|
|
var writtenServerReferences = request.writtenServerReferences;
|
|
var existingId = writtenServerReferences.get(serverReference);
|
|
|
|
if (existingId !== undefined) {
|
|
return serializeServerReferenceID(existingId);
|
|
}
|
|
|
|
var bound = getServerReferenceBoundArguments();
|
|
var serverReferenceMetadata = {
|
|
id: getServerReferenceId(),
|
|
bound: bound ? Promise.resolve(bound) : null
|
|
};
|
|
var metadataId = outlineModel(request, serverReferenceMetadata);
|
|
writtenServerReferences.set(serverReference, metadataId);
|
|
return serializeServerReferenceID(metadataId);
|
|
}
|
|
|
|
function serializeLargeTextString(request, text) {
|
|
request.pendingChunks += 2;
|
|
var textId = request.nextChunkId++;
|
|
var textChunk = stringToChunk(text);
|
|
var binaryLength = byteLengthOfChunk(textChunk);
|
|
var row = textId.toString(16) + ":T" + binaryLength.toString(16) + ",";
|
|
var headerChunk = stringToChunk(row);
|
|
request.completedRegularChunks.push(headerChunk, textChunk);
|
|
return serializeByValueID(textId);
|
|
}
|
|
|
|
function serializeMap(request, map) {
|
|
var entries = Array.from(map);
|
|
|
|
for (var i = 0; i < entries.length; i++) {
|
|
var key = entries[i][0];
|
|
|
|
if (typeof key === "object" && key !== null) {
|
|
var writtenObjects = request.writtenObjects;
|
|
var existingId = writtenObjects.get(key);
|
|
|
|
if (existingId === undefined) {
|
|
// Mark all object keys as seen so that they're always outlined.
|
|
writtenObjects.set(key, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
var id = outlineModel(request, entries);
|
|
return "$Q" + id.toString(16);
|
|
}
|
|
|
|
function serializeSet(request, set) {
|
|
var entries = Array.from(set);
|
|
|
|
for (var i = 0; i < entries.length; i++) {
|
|
var key = entries[i];
|
|
|
|
if (typeof key === "object" && key !== null) {
|
|
var writtenObjects = request.writtenObjects;
|
|
var existingId = writtenObjects.get(key);
|
|
|
|
if (existingId === undefined) {
|
|
// Mark all object keys as seen so that they're always outlined.
|
|
writtenObjects.set(key, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
var id = outlineModel(request, entries);
|
|
return "$W" + id.toString(16);
|
|
}
|
|
|
|
function escapeStringValue(value) {
|
|
if (value[0] === "$") {
|
|
// We need to escape $ prefixed strings since we use those to encode
|
|
// references to IDs and as special symbol values.
|
|
return "$" + value;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
var modelRoot = false;
|
|
|
|
function renderModel(request, task, parent, key, value) {
|
|
var prevKeyPath = task.keyPath;
|
|
var prevImplicitSlot = task.implicitSlot;
|
|
|
|
try {
|
|
return renderModelDestructive(request, task, parent, key, value);
|
|
} catch (thrownValue) {
|
|
var x =
|
|
thrownValue === SuspenseException // This is a special type of exception used for Suspense. For historical
|
|
? // reasons, the rest of the Suspense implementation expects the thrown
|
|
// value to be a thenable, because before `use` existed that was the
|
|
// (unstable) API for suspending. This implementation detail can change
|
|
// later, once we deprecate the old API in favor of `use`.
|
|
getSuspendedThenable()
|
|
: thrownValue; // If the suspended/errored value was an element or lazy it can be reduced
|
|
// to a lazy reference, so that it doesn't error the parent.
|
|
|
|
var model = task.model;
|
|
var wasReactNode =
|
|
typeof model === "object" &&
|
|
model !== null &&
|
|
(model.$$typeof === REACT_ELEMENT_TYPE ||
|
|
model.$$typeof === REACT_LAZY_TYPE);
|
|
|
|
if (typeof x === "object" && x !== null) {
|
|
// $FlowFixMe[method-unbinding]
|
|
if (typeof x.then === "function") {
|
|
// Something suspended, we'll need to create a new task and resolve it later.
|
|
var newTask = createTask(
|
|
request,
|
|
task.model,
|
|
task.keyPath,
|
|
task.implicitSlot,
|
|
request.abortableTasks
|
|
);
|
|
var ping = newTask.ping;
|
|
x.then(ping, ping);
|
|
newTask.thenableState = getThenableStateAfterSuspending(); // Restore the context. We assume that this will be restored by the inner
|
|
// functions in case nothing throws so we don't use "finally" here.
|
|
|
|
task.keyPath = prevKeyPath;
|
|
task.implicitSlot = prevImplicitSlot;
|
|
|
|
if (wasReactNode) {
|
|
return serializeLazyID(newTask.id);
|
|
}
|
|
|
|
return serializeByValueID(newTask.id);
|
|
}
|
|
} // Restore the context. We assume that this will be restored by the inner
|
|
// functions in case nothing throws so we don't use "finally" here.
|
|
|
|
task.keyPath = prevKeyPath;
|
|
task.implicitSlot = prevImplicitSlot;
|
|
|
|
if (wasReactNode) {
|
|
// Something errored. We'll still send everything we have up until this point.
|
|
// We'll replace this element with a lazy reference that throws on the client
|
|
// once it gets rendered.
|
|
request.pendingChunks++;
|
|
var errorId = request.nextChunkId++;
|
|
var digest = logRecoverableError(request, x);
|
|
emitErrorChunk(request, errorId, digest, x);
|
|
return serializeLazyID(errorId);
|
|
} // Something errored but it was not in a React Node. There's no need to serialize
|
|
// it by value because it'll just error the whole parent row anyway so we can
|
|
// just stop any siblings and error the whole parent row.
|
|
|
|
throw x;
|
|
}
|
|
}
|
|
|
|
function renderModelDestructive(
|
|
request,
|
|
task,
|
|
parent,
|
|
parentPropertyName,
|
|
value
|
|
) {
|
|
// Set the currently rendering model
|
|
task.model = value; // Special Symbol, that's very common.
|
|
|
|
if (value === REACT_ELEMENT_TYPE) {
|
|
return "$";
|
|
}
|
|
|
|
if (value === null) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof value === "object") {
|
|
switch (value.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
var _writtenObjects = request.writtenObjects;
|
|
|
|
var _existingId = _writtenObjects.get(value);
|
|
|
|
if (_existingId !== undefined) {
|
|
if (task.keyPath !== null || task.implicitSlot);
|
|
else if (modelRoot === value) {
|
|
// This is the ID we're currently emitting so we need to write it
|
|
// once but if we discover it again, we refer to it by id.
|
|
modelRoot = null;
|
|
} else if (_existingId === -1) {
|
|
// Seen but not yet outlined.
|
|
// TODO: If we throw here we can treat this as suspending which causes an outline
|
|
// but that is able to reuse the same task if we're already in one but then that
|
|
// will be a lazy future value rather than guaranteed to exist but maybe that's good.
|
|
var newId = outlineModel(request, value);
|
|
return serializeByValueID(newId);
|
|
} else {
|
|
// We've already emitted this as an outlined object, so we can refer to that by its
|
|
// existing ID. TODO: We should use a lazy reference since, unlike plain objects,
|
|
// elements might suspend so it might not have emitted yet even if we have the ID for
|
|
// it. However, this creates an extra wrapper when it's not needed. We should really
|
|
// detect whether this already was emitted and synchronously available. In that
|
|
// case we can refer to it synchronously and only make it lazy otherwise.
|
|
// We currently don't have a data structure that lets us see that though.
|
|
return serializeByValueID(_existingId);
|
|
}
|
|
} else {
|
|
// This is the first time we've seen this object. We may never see it again
|
|
// so we'll inline it. Mark it as seen. If we see it again, we'll outline.
|
|
_writtenObjects.set(value, -1);
|
|
}
|
|
|
|
var element = value; // Attempt to render the Server Component.
|
|
|
|
return renderElement(
|
|
request,
|
|
task,
|
|
element.type, // $FlowFixMe[incompatible-call] the key of an element is null | string
|
|
element.key,
|
|
element.ref,
|
|
element.props
|
|
);
|
|
}
|
|
|
|
case REACT_LAZY_TYPE: {
|
|
var payload = value._payload;
|
|
var init = value._init;
|
|
var resolvedModel = init(payload);
|
|
return renderModelDestructive(
|
|
request,
|
|
task,
|
|
emptyRoot,
|
|
"",
|
|
resolvedModel
|
|
);
|
|
}
|
|
}
|
|
|
|
if (isClientReference(value)) {
|
|
return serializeClientReference(
|
|
request,
|
|
parent,
|
|
parentPropertyName,
|
|
value
|
|
);
|
|
}
|
|
|
|
var writtenObjects = request.writtenObjects;
|
|
var existingId = writtenObjects.get(value); // $FlowFixMe[method-unbinding]
|
|
|
|
if (typeof value.then === "function") {
|
|
if (existingId !== undefined) {
|
|
if (task.keyPath !== null || task.implicitSlot) {
|
|
// If we're in some kind of context we can't reuse the result of this render or
|
|
// previous renders of this element. We only reuse Promises if they're not wrapped
|
|
// by another Server Component.
|
|
var _promiseId = serializeThenable(request, task, value);
|
|
|
|
return serializePromiseID(_promiseId);
|
|
} else if (modelRoot === value) {
|
|
// This is the ID we're currently emitting so we need to write it
|
|
// once but if we discover it again, we refer to it by id.
|
|
modelRoot = null;
|
|
} else {
|
|
// We've seen this promise before, so we can just refer to the same result.
|
|
return serializePromiseID(existingId);
|
|
}
|
|
} // We assume that any object with a .then property is a "Thenable" type,
|
|
// or a Promise type. Either of which can be represented by a Promise.
|
|
|
|
var promiseId = serializeThenable(request, task, value);
|
|
writtenObjects.set(value, promiseId);
|
|
return serializePromiseID(promiseId);
|
|
}
|
|
|
|
if (existingId !== undefined) {
|
|
if (modelRoot === value) {
|
|
// This is the ID we're currently emitting so we need to write it
|
|
// once but if we discover it again, we refer to it by id.
|
|
modelRoot = null;
|
|
} else if (existingId === -1) {
|
|
// Seen but not yet outlined.
|
|
var _newId = outlineModel(request, value);
|
|
|
|
return serializeByValueID(_newId);
|
|
} else {
|
|
// We've already emitted this as an outlined object, so we can
|
|
// just refer to that by its existing ID.
|
|
return serializeByValueID(existingId);
|
|
}
|
|
} else {
|
|
// This is the first time we've seen this object. We may never see it again
|
|
// so we'll inline it. Mark it as seen. If we see it again, we'll outline.
|
|
writtenObjects.set(value, -1);
|
|
}
|
|
|
|
if (isArray(value)) {
|
|
return renderFragment(request, task, value);
|
|
}
|
|
|
|
if (value instanceof Map) {
|
|
return serializeMap(request, value);
|
|
}
|
|
|
|
if (value instanceof Set) {
|
|
return serializeSet(request, value);
|
|
}
|
|
|
|
var iteratorFn = getIteratorFn(value);
|
|
|
|
if (iteratorFn) {
|
|
return renderFragment(request, task, Array.from(value));
|
|
} // Verify that this is a simple plain object.
|
|
|
|
var proto = getPrototypeOf(value);
|
|
|
|
if (
|
|
proto !== ObjectPrototype &&
|
|
(proto === null || getPrototypeOf(proto) !== null)
|
|
) {
|
|
throw new Error(
|
|
"Only plain objects, and a few built-ins, can be passed to Client Components " +
|
|
"from Server Components. Classes or null prototypes are not supported."
|
|
);
|
|
}
|
|
|
|
{
|
|
if (objectName(value) !== "Object") {
|
|
error(
|
|
"Only plain objects can be passed to Client Components from Server Components. " +
|
|
"%s objects are not supported.%s",
|
|
objectName(value),
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
} else if (!isSimpleObject(value)) {
|
|
error(
|
|
"Only plain objects can be passed to Client Components from Server Components. " +
|
|
"Classes or other objects with methods are not supported.%s",
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
} else if (Object.getOwnPropertySymbols) {
|
|
var symbols = Object.getOwnPropertySymbols(value);
|
|
|
|
if (symbols.length > 0) {
|
|
error(
|
|
"Only plain objects can be passed to Client Components from Server Components. " +
|
|
"Objects with symbol properties like %s are not supported.%s",
|
|
symbols[0].description,
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
}
|
|
}
|
|
} // $FlowFixMe[incompatible-return]
|
|
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === "string") {
|
|
if (value[value.length - 1] === "Z") {
|
|
// Possibly a Date, whose toJSON automatically calls toISOString
|
|
// $FlowFixMe[incompatible-use]
|
|
var originalValue = parent[parentPropertyName];
|
|
|
|
if (originalValue instanceof Date) {
|
|
return serializeDateFromDateJSON(value);
|
|
}
|
|
}
|
|
|
|
if (value.length >= 1024) {
|
|
// For large strings, we encode them outside the JSON payload so that we
|
|
// don't have to double encode and double parse the strings. This can also
|
|
// be more compact in case the string has a lot of escaped characters.
|
|
return serializeLargeTextString(request, value);
|
|
}
|
|
|
|
return escapeStringValue(value);
|
|
}
|
|
|
|
if (typeof value === "boolean") {
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === "number") {
|
|
return serializeNumber(value);
|
|
}
|
|
|
|
if (typeof value === "undefined") {
|
|
return serializeUndefined();
|
|
}
|
|
|
|
if (typeof value === "function") {
|
|
if (isClientReference(value)) {
|
|
return serializeClientReference(
|
|
request,
|
|
parent,
|
|
parentPropertyName,
|
|
value
|
|
);
|
|
}
|
|
|
|
if (isServerReference()) {
|
|
return serializeServerReference(request, value);
|
|
}
|
|
|
|
if (/^on[A-Z]/.test(parentPropertyName)) {
|
|
throw new Error(
|
|
"Event handlers cannot be passed to Client Component props." +
|
|
describeObjectForErrorMessage(parent, parentPropertyName) +
|
|
"\nIf you need interactivity, consider converting part of this to a Client Component."
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
"Functions cannot be passed directly to Client Components " +
|
|
'unless you explicitly expose it by marking it with "use server".' +
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (typeof value === "symbol") {
|
|
var writtenSymbols = request.writtenSymbols;
|
|
|
|
var _existingId2 = writtenSymbols.get(value);
|
|
|
|
if (_existingId2 !== undefined) {
|
|
return serializeByValueID(_existingId2);
|
|
} // $FlowFixMe[incompatible-type] `description` might be undefined
|
|
|
|
var name = value.description;
|
|
|
|
if (Symbol.for(name) !== value) {
|
|
throw new Error(
|
|
"Only global symbols received from Symbol.for(...) can be passed to Client Components. " +
|
|
("The symbol Symbol.for(" + // $FlowFixMe[incompatible-type] `description` might be undefined
|
|
value.description +
|
|
") cannot be found among global symbols.") +
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
}
|
|
|
|
request.pendingChunks++;
|
|
var symbolId = request.nextChunkId++;
|
|
emitSymbolChunk(request, symbolId, name);
|
|
writtenSymbols.set(value, symbolId);
|
|
return serializeByValueID(symbolId);
|
|
}
|
|
|
|
if (typeof value === "bigint") {
|
|
return serializeBigInt(value);
|
|
}
|
|
|
|
throw new Error(
|
|
"Type " +
|
|
typeof value +
|
|
" is not supported in Client Component props." +
|
|
describeObjectForErrorMessage(parent, parentPropertyName)
|
|
);
|
|
}
|
|
|
|
function logRecoverableError(request, error) {
|
|
var onError = request.onError;
|
|
var errorDigest = onError(error);
|
|
|
|
if (errorDigest != null && typeof errorDigest !== "string") {
|
|
// eslint-disable-next-line react-internal/prod-error-codes
|
|
throw new Error(
|
|
'onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type "' +
|
|
typeof errorDigest +
|
|
'" instead'
|
|
);
|
|
}
|
|
|
|
return errorDigest || "";
|
|
}
|
|
|
|
function fatalError(request, error) {
|
|
if (request.destination !== null) {
|
|
request.status = CLOSED;
|
|
closeWithError(request.destination, error);
|
|
} else {
|
|
request.status = CLOSING;
|
|
request.fatalError = error;
|
|
}
|
|
}
|
|
|
|
function emitErrorChunk(request, id, digest, error) {
|
|
var errorInfo;
|
|
|
|
{
|
|
var message;
|
|
var stack = "";
|
|
|
|
try {
|
|
if (error instanceof Error) {
|
|
// eslint-disable-next-line react-internal/safe-string-coercion
|
|
message = String(error.message); // eslint-disable-next-line react-internal/safe-string-coercion
|
|
|
|
stack = String(error.stack);
|
|
} else {
|
|
message = "Error: " + error;
|
|
}
|
|
} catch (x) {
|
|
message =
|
|
"An error occurred but serializing the error message failed.";
|
|
}
|
|
|
|
errorInfo = {
|
|
digest: digest,
|
|
message: message,
|
|
stack: stack
|
|
};
|
|
}
|
|
|
|
var row = serializeRowHeader("E", id) + stringify(errorInfo) + "\n";
|
|
var processedChunk = stringToChunk(row);
|
|
request.completedErrorChunks.push(processedChunk);
|
|
}
|
|
|
|
function emitImportChunk(request, id, clientReferenceMetadata) {
|
|
// $FlowFixMe[incompatible-type] stringify can return null
|
|
var json = stringify(clientReferenceMetadata);
|
|
var row = serializeRowHeader("I", id) + json + "\n";
|
|
var processedChunk = stringToChunk(row);
|
|
request.completedImportChunks.push(processedChunk);
|
|
}
|
|
|
|
function emitHintChunk(request, code, model) {
|
|
var json = stringify(model);
|
|
var id = request.nextChunkId++;
|
|
var row = serializeRowHeader("H" + code, id) + json + "\n";
|
|
var processedChunk = stringToChunk(row);
|
|
request.completedHintChunks.push(processedChunk);
|
|
}
|
|
|
|
function emitSymbolChunk(request, id, name) {
|
|
var symbolReference = serializeSymbolReference(name);
|
|
var processedChunk = encodeReferenceChunk(request, id, symbolReference);
|
|
request.completedImportChunks.push(processedChunk);
|
|
}
|
|
|
|
function emitModelChunk(request, id, json) {
|
|
var row = id.toString(16) + ":" + json + "\n";
|
|
var processedChunk = stringToChunk(row);
|
|
request.completedRegularChunks.push(processedChunk);
|
|
}
|
|
|
|
function emitDebugChunk(request, id, debugInfo) {
|
|
var json = stringify(debugInfo);
|
|
var row = serializeRowHeader("D", id) + json + "\n";
|
|
var processedChunk = stringToChunk(row);
|
|
request.completedRegularChunks.push(processedChunk);
|
|
}
|
|
|
|
var emptyRoot = {};
|
|
|
|
function retryTask(request, task) {
|
|
if (task.status !== PENDING) {
|
|
// We completed this by other means before we had a chance to retry it.
|
|
return;
|
|
}
|
|
|
|
var prevDebugID = debugID;
|
|
|
|
try {
|
|
// Track the root so we know that we have to emit this object even though it
|
|
// already has an ID. This is needed because we might see this object twice
|
|
// in the same toJSON if it is cyclic.
|
|
modelRoot = task.model;
|
|
|
|
if (true) {
|
|
// Track the ID of the current task so we can assign debug info to this id.
|
|
debugID = task.id;
|
|
} // We call the destructive form that mutates this task. That way if something
|
|
// suspends again, we can reuse the same task instead of spawning a new one.
|
|
|
|
var resolvedModel = renderModelDestructive(
|
|
request,
|
|
task,
|
|
emptyRoot,
|
|
"",
|
|
task.model
|
|
);
|
|
|
|
if (true) {
|
|
// We're now past rendering this task and future renders will spawn new tasks for their
|
|
// debug info.
|
|
debugID = null;
|
|
} // Track the root again for the resolved object.
|
|
|
|
modelRoot = resolvedModel; // The keyPath resets at any terminal child node.
|
|
|
|
task.keyPath = null;
|
|
task.implicitSlot = false;
|
|
var json;
|
|
|
|
if (typeof resolvedModel === "object" && resolvedModel !== null) {
|
|
// Object might contain unresolved values like additional elements.
|
|
// This is simulating what the JSON loop would do if this was part of it.
|
|
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
|
|
json = stringify(resolvedModel, task.toJSON);
|
|
} else {
|
|
// If the value is a string, it means it's a terminal value and we already escaped it
|
|
// We don't need to escape it again so it's not passed the toJSON replacer.
|
|
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
|
|
json = stringify(resolvedModel);
|
|
}
|
|
|
|
emitModelChunk(request, task.id, json);
|
|
request.abortableTasks.delete(task);
|
|
task.status = COMPLETED;
|
|
} catch (thrownValue) {
|
|
var x =
|
|
thrownValue === SuspenseException // This is a special type of exception used for Suspense. For historical
|
|
? // reasons, the rest of the Suspense implementation expects the thrown
|
|
// value to be a thenable, because before `use` existed that was the
|
|
// (unstable) API for suspending. This implementation detail can change
|
|
// later, once we deprecate the old API in favor of `use`.
|
|
getSuspendedThenable()
|
|
: thrownValue;
|
|
|
|
if (typeof x === "object" && x !== null) {
|
|
// $FlowFixMe[method-unbinding]
|
|
if (typeof x.then === "function") {
|
|
// Something suspended again, let's pick it back up later.
|
|
var ping = task.ping;
|
|
x.then(ping, ping);
|
|
task.thenableState = getThenableStateAfterSuspending();
|
|
return;
|
|
}
|
|
}
|
|
|
|
request.abortableTasks.delete(task);
|
|
task.status = ERRORED;
|
|
var digest = logRecoverableError(request, x);
|
|
emitErrorChunk(request, task.id, digest, x);
|
|
} finally {
|
|
{
|
|
debugID = prevDebugID;
|
|
}
|
|
}
|
|
}
|
|
|
|
function performWork(request) {
|
|
var prevDispatcher = ReactCurrentDispatcher.current;
|
|
ReactCurrentDispatcher.current = HooksDispatcher;
|
|
var prevRequest = currentRequest;
|
|
currentRequest = request;
|
|
prepareToUseHooksForRequest(request);
|
|
|
|
try {
|
|
var pingedTasks = request.pingedTasks;
|
|
request.pingedTasks = [];
|
|
|
|
for (var i = 0; i < pingedTasks.length; i++) {
|
|
var task = pingedTasks[i];
|
|
retryTask(request, task);
|
|
}
|
|
|
|
if (request.destination !== null) {
|
|
flushCompletedChunks(request, request.destination);
|
|
}
|
|
} catch (error) {
|
|
logRecoverableError(request, error);
|
|
fatalError(request, error);
|
|
} finally {
|
|
ReactCurrentDispatcher.current = prevDispatcher;
|
|
resetHooksForRequest();
|
|
currentRequest = prevRequest;
|
|
}
|
|
}
|
|
|
|
function flushCompletedChunks(request, destination) {
|
|
beginWriting(destination);
|
|
|
|
try {
|
|
// We emit module chunks first in the stream so that
|
|
// they can be preloaded as early as possible.
|
|
var importsChunks = request.completedImportChunks;
|
|
var i = 0;
|
|
|
|
for (; i < importsChunks.length; i++) {
|
|
request.pendingChunks--;
|
|
var chunk = importsChunks[i];
|
|
var keepWriting = writeChunkAndReturn(destination, chunk);
|
|
|
|
if (!keepWriting) {
|
|
request.destination = null;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
importsChunks.splice(0, i); // Next comes hints.
|
|
|
|
var hintChunks = request.completedHintChunks;
|
|
i = 0;
|
|
|
|
for (; i < hintChunks.length; i++) {
|
|
var _chunk = hintChunks[i];
|
|
|
|
var _keepWriting = writeChunkAndReturn(destination, _chunk);
|
|
|
|
if (!_keepWriting) {
|
|
request.destination = null;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hintChunks.splice(0, i); // Next comes model data.
|
|
|
|
var regularChunks = request.completedRegularChunks;
|
|
i = 0;
|
|
|
|
for (; i < regularChunks.length; i++) {
|
|
request.pendingChunks--;
|
|
var _chunk2 = regularChunks[i];
|
|
|
|
var _keepWriting2 = writeChunkAndReturn(destination, _chunk2);
|
|
|
|
if (!_keepWriting2) {
|
|
request.destination = null;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
regularChunks.splice(0, i); // Finally, errors are sent. The idea is that it's ok to delay
|
|
// any error messages and prioritize display of other parts of
|
|
// the page.
|
|
|
|
var errorChunks = request.completedErrorChunks;
|
|
i = 0;
|
|
|
|
for (; i < errorChunks.length; i++) {
|
|
request.pendingChunks--;
|
|
var _chunk3 = errorChunks[i];
|
|
|
|
var _keepWriting3 = writeChunkAndReturn(destination, _chunk3);
|
|
|
|
if (!_keepWriting3) {
|
|
request.destination = null;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
errorChunks.splice(0, i);
|
|
} finally {
|
|
request.flushScheduled = false;
|
|
completeWriting(destination);
|
|
}
|
|
|
|
flushBuffered(destination);
|
|
|
|
if (request.pendingChunks === 0) {
|
|
close(destination);
|
|
}
|
|
}
|
|
|
|
function startWork(request) {
|
|
request.flushScheduled = request.destination !== null;
|
|
|
|
{
|
|
scheduleWork(function () {
|
|
return performWork(request);
|
|
});
|
|
}
|
|
}
|
|
|
|
function enqueueFlush(request) {
|
|
if (
|
|
request.flushScheduled === false && // If there are pinged tasks we are going to flush anyway after work completes
|
|
request.pingedTasks.length === 0 && // If there is no destination there is nothing we can flush to. A flush will
|
|
// happen when we start flowing again
|
|
request.destination !== null
|
|
) {
|
|
var destination = request.destination;
|
|
request.flushScheduled = true;
|
|
scheduleWork(function () {
|
|
return flushCompletedChunks(request, destination);
|
|
});
|
|
}
|
|
}
|
|
|
|
function startFlowing(request, destination) {
|
|
if (request.status === CLOSING) {
|
|
request.status = CLOSED;
|
|
closeWithError(destination, request.fatalError);
|
|
return;
|
|
}
|
|
|
|
if (request.status === CLOSED) {
|
|
return;
|
|
}
|
|
|
|
if (request.destination !== null) {
|
|
// We're already flowing.
|
|
return;
|
|
}
|
|
|
|
request.destination = destination;
|
|
|
|
try {
|
|
flushCompletedChunks(request, destination);
|
|
} catch (error) {
|
|
logRecoverableError(request, error);
|
|
fatalError(request, error);
|
|
}
|
|
}
|
|
|
|
function renderToDestination(destination, model, options) {
|
|
if (!configured) {
|
|
throw new Error(
|
|
"Please make sure to call `setConfig(...)` before calling `renderToDestination`."
|
|
);
|
|
}
|
|
|
|
var request = createRequest(
|
|
model,
|
|
null,
|
|
options ? options.onError : undefined
|
|
);
|
|
startWork(request);
|
|
startFlowing(request, destination);
|
|
}
|
|
|
|
var configured = false;
|
|
|
|
function setConfig(config) {
|
|
setByteLengthOfChunkImplementation(config.byteLength);
|
|
setCheckIsClientReference(config.isClientReference);
|
|
configured = true;
|
|
}
|
|
|
|
exports.clearRequestedClientReferencesKeysSet =
|
|
clearRequestedClientReferencesKeysSet;
|
|
exports.getRequestedClientReferencesKeys = getRequestedClientReferencesKeys;
|
|
exports.registerClientReference = registerClientReference;
|
|
exports.registerServerReference = registerServerReference;
|
|
exports.renderToDestination = renderToDestination;
|
|
exports.setCheckIsClientReference = setCheckIsClientReference;
|
|
exports.setConfig = setConfig;
|
|
})();
|
|
}
|