UNPKG

dinou

Version:

Dinou is a modern React 19 framework with React Server Components, Server Functions, and streaming SSR.

1,370 lines (1,343 loc) 93.2 kB
/** * @license React * react-server-dom-esm-client.browser.production.js * * 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. */ import * as ReactDOM from 'react-dom'; function createStringDecoder() { return new TextDecoder(); } const decoderOptions = { stream: true }; function readPartialStringChunk(decoder, buffer) { return decoder.decode(buffer, decoderOptions); } function readFinalStringChunk(decoder, buffer) { return decoder.decode(buffer); } // Module root path function resolveClientReference(bundlerConfig, metadata) { const baseURL = bundlerConfig; return { specifier: baseURL + metadata[0], name: metadata[1] }; } function resolveServerReference(config, id) { const baseURL = config; const idx = id.lastIndexOf('#'); const exportName = id.slice(idx + 1); const fullURL = id.slice(0, idx); if (!fullURL.startsWith(baseURL)) { throw new Error('Attempted to load a Server Reference outside the hosted root.'); } return { specifier: fullURL, name: exportName }; } const asyncModuleCache = new Map(); function preloadModule(metadata) { const existingPromise = asyncModuleCache.get(metadata.specifier); if (existingPromise) { if (existingPromise.status === 'fulfilled') { return null; } return existingPromise; } else { // $FlowFixMe[unsupported-syntax] const modulePromise = import(metadata.specifier); modulePromise.then(value => { const fulfilledThenable = modulePromise; fulfilledThenable.status = 'fulfilled'; fulfilledThenable.value = value; }, reason => { const rejectedThenable = modulePromise; rejectedThenable.status = 'rejected'; rejectedThenable.reason = reason; }); asyncModuleCache.set(metadata.specifier, modulePromise); return modulePromise; } } function requireModule(metadata) { let moduleExports; // We assume that preloadModule has been called before, which // should have added something to the module cache. const promise = asyncModuleCache.get(metadata.specifier); if (promise.status === 'fulfilled') { moduleExports = promise.value; } else { throw promise.reason; } return moduleExports[metadata.name]; } const ReactDOMSharedInternals = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; // This client file is in the shared folder because it applies to both SSR and browser contexts. // It is the configuration of the FlightClient behavior which can run in either environment. function dispatchHint(code, model) { const dispatcher = ReactDOMSharedInternals.d; /* ReactDOMCurrentDispatcher */ switch (code) { case 'D': { const refined = refineModel(code, model); const href = refined; dispatcher.D( /* prefetchDNS */href); return; } case 'C': { const refined = refineModel(code, model); if (typeof refined === 'string') { const href = refined; dispatcher.C( /* preconnect */href); } else { const href = refined[0]; const crossOrigin = refined[1]; dispatcher.C( /* preconnect */href, crossOrigin); } return; } case 'L': { const refined = refineModel(code, model); const href = refined[0]; const as = refined[1]; if (refined.length === 3) { const options = refined[2]; dispatcher.L( /* preload */href, as, options); } else { dispatcher.L( /* preload */href, as); } return; } case 'm': { const refined = refineModel(code, model); if (typeof refined === 'string') { const href = refined; dispatcher.m( /* preloadModule */href); } else { const href = refined[0]; const options = refined[1]; dispatcher.m( /* preloadModule */href, options); } return; } case 'X': { const refined = refineModel(code, model); if (typeof refined === 'string') { const href = refined; dispatcher.X( /* preinitScript */href); } else { const href = refined[0]; const options = refined[1]; dispatcher.X( /* preinitScript */href, options); } return; } case 'S': { const refined = refineModel(code, model); if (typeof refined === 'string') { const href = refined; dispatcher.S( /* preinitStyle */href); } else { const href = refined[0]; const precedence = refined[1] === 0 ? undefined : refined[1]; const options = refined.length === 3 ? refined[2] : undefined; dispatcher.S( /* preinitStyle */href, precedence, options); } return; } case 'M': { const refined = refineModel(code, model); if (typeof refined === 'string') { const href = refined; dispatcher.M( /* preinitModuleScript */href); } else { const href = refined[0]; const options = refined[1]; dispatcher.M( /* preinitModuleScript */href, options); } return; } } } // Flow is having trouble refining the HintModels so we help it a bit. // This should be compiled out in the production build. function refineModel(code, model) { return model; } const REACT_ELEMENT_TYPE = Symbol.for('react.transitional.element') ; const REACT_LAZY_TYPE = Symbol.for('react.lazy'); const REACT_POSTPONE_TYPE = Symbol.for('react.postpone'); const MAYBE_ITERATOR_SYMBOL = Symbol.iterator; const FAUX_ITERATOR_SYMBOL = '@@iterator'; function getIteratorFn(maybeIterable) { if (maybeIterable === null || typeof maybeIterable !== 'object') { return null; } const maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]; if (typeof maybeIterator === 'function') { return maybeIterator; } return null; } const ASYNC_ITERATOR = Symbol.asyncIterator; const isArrayImpl = Array.isArray; function isArray(a) { return isArrayImpl(a); } const getPrototypeOf = Object.getPrototypeOf; function createTemporaryReferenceSet() { return new Map(); } function writeTemporaryReference(set, reference, object) { set.set(reference, object); } function readTemporaryReference(set, reference) { return set.get(reference); } const ObjectPrototype = Object.prototype; const knownServerReferences = new WeakMap(); // Serializable values // Thenable<ReactServerValue> function serializeByValueID(id) { return '$' + id.toString(16); } function serializePromiseID(id) { return '$@' + id.toString(16); } function serializeServerReferenceID(id) { return '$F' + id.toString(16); } function serializeTemporaryReferenceMarker() { return '$T'; } function serializeFormDataReference(id) { // Why K? F is "Function". D is "Date". What else? return '$K' + id.toString(16); } 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 serializeMapID(id) { return '$Q' + id.toString(16); } function serializeSetID(id) { return '$W' + id.toString(16); } function serializeBlobID(id) { return '$B' + id.toString(16); } function serializeIteratorID(id) { return '$i' + 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; } } function processReply(root, formFieldPrefix, temporaryReferences, resolve, reject) { let nextPartId = 1; let pendingParts = 0; let formData = null; const writtenObjects = new WeakMap(); let modelRoot = root; function serializeTypedArray(tag, typedArray) { const blob = new Blob([ // We should be able to pass the buffer straight through but Node < 18 treat // multi-byte array blobs differently so we first convert it to single-byte. new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength)]); const blobId = nextPartId++; if (formData === null) { formData = new FormData(); } formData.append(formFieldPrefix + blobId, blob); return '$' + tag + blobId.toString(16); } function serializeBinaryReader(reader) { if (formData === null) { // Upgrade to use FormData to allow us to stream this value. formData = new FormData(); } const data = formData; pendingParts++; const streamId = nextPartId++; const buffer = []; function progress(entry) { if (entry.done) { const blobId = nextPartId++; data.append(formFieldPrefix + blobId, new Blob(buffer)); data.append(formFieldPrefix + streamId, '"$o' + blobId.toString(16) + '"'); data.append(formFieldPrefix + streamId, 'C'); // Close signal pendingParts--; if (pendingParts === 0) { resolve(data); } } else { buffer.push(entry.value); reader.read(new Uint8Array(1024)).then(progress, reject); } } reader.read(new Uint8Array(1024)).then(progress, reject); return '$r' + streamId.toString(16); } function serializeReader(reader) { if (formData === null) { // Upgrade to use FormData to allow us to stream this value. formData = new FormData(); } const data = formData; pendingParts++; const streamId = nextPartId++; function progress(entry) { if (entry.done) { data.append(formFieldPrefix + streamId, 'C'); // Close signal pendingParts--; if (pendingParts === 0) { resolve(data); } } else { try { // $FlowFixMe[incompatible-type]: While plain JSON can return undefined we never do here. const partJSON = JSON.stringify(entry.value, resolveToJSON); data.append(formFieldPrefix + streamId, partJSON); reader.read().then(progress, reject); } catch (x) { reject(x); } } } reader.read().then(progress, reject); return '$R' + streamId.toString(16); } function serializeReadableStream(stream) { // Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the // receiving side. For binary streams, we serialize them as plain Blobs. let binaryReader; try { // $FlowFixMe[extra-arg]: This argument is accepted. binaryReader = stream.getReader({ mode: 'byob' }); } catch (x) { return serializeReader(stream.getReader()); } return serializeBinaryReader(binaryReader); } function serializeAsyncIterable(iterable, iterator) { if (formData === null) { // Upgrade to use FormData to allow us to stream this value. formData = new FormData(); } const data = formData; pendingParts++; const streamId = nextPartId++; // Generators/Iterators are Iterables but they're also their own iterator // functions. If that's the case, we treat them as single-shot. Otherwise, // we assume that this iterable might be a multi-shot and allow it to be // iterated more than once on the receiving server. const isIterator = iterable === iterator; // There's a race condition between when the stream is aborted and when the promise // resolves so we track whether we already aborted it to avoid writing twice. function progress(entry) { if (entry.done) { if (entry.value === undefined) { data.append(formFieldPrefix + streamId, 'C'); // Close signal } else { // Unlike streams, the last value may not be undefined. If it's not // we outline it and encode a reference to it in the closing instruction. try { // $FlowFixMe[incompatible-type]: While plain JSON can return undefined we never do here. const partJSON = JSON.stringify(entry.value, resolveToJSON); data.append(formFieldPrefix + streamId, 'C' + partJSON); // Close signal } catch (x) { reject(x); return; } } pendingParts--; if (pendingParts === 0) { resolve(data); } } else { try { // $FlowFixMe[incompatible-type]: While plain JSON can return undefined we never do here. const partJSON = JSON.stringify(entry.value, resolveToJSON); data.append(formFieldPrefix + streamId, partJSON); iterator.next().then(progress, reject); } catch (x) { reject(x); return; } } } iterator.next().then(progress, reject); return '$' + (isIterator ? 'x' : 'X') + streamId.toString(16); } function resolveToJSON(key, value) { const parent = this; if (value === null) { return null; } if (typeof value === 'object') { switch (value.$$typeof) { case REACT_ELEMENT_TYPE: { if (temporaryReferences !== undefined && key.indexOf(':') === -1) { // TODO: If the property name contains a colon, we don't dedupe. Escape instead. const parentReference = writtenObjects.get(parent); if (parentReference !== undefined) { // If the parent has a reference, we can refer to this object indirectly // through the property name inside that parent. const reference = parentReference + ':' + key; // Store this object so that the server can refer to it later in responses. writeTemporaryReference(temporaryReferences, reference, value); return serializeTemporaryReferenceMarker(); } } throw new Error('React Element cannot be passed to Server Functions from the Client without a ' + 'temporary reference set. Pass a TemporaryReferenceSet to the options.' + ('')); } case REACT_LAZY_TYPE: { // Resolve lazy as if it wasn't here. In the future this will be encoded as a Promise. const lazy = value; const payload = lazy._payload; const init = lazy._init; if (formData === null) { // Upgrade to use FormData to allow us to stream this value. formData = new FormData(); } pendingParts++; try { const resolvedModel = init(payload); // We always outline this as a separate part even though we could inline it // because it ensures a more deterministic encoding. const lazyId = nextPartId++; const partJSON = serializeModel(resolvedModel, lazyId); // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above. const data = formData; data.append(formFieldPrefix + lazyId, partJSON); return serializeByValueID(lazyId); } catch (x) { if (typeof x === 'object' && x !== null && typeof x.then === 'function') { // Suspended pendingParts++; const lazyId = nextPartId++; const thenable = x; const retry = function () { // While the first promise resolved, its value isn't necessarily what we'll // resolve into because we might suspend again. try { const partJSON = serializeModel(value, lazyId); // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above. const data = formData; data.append(formFieldPrefix + lazyId, partJSON); pendingParts--; if (pendingParts === 0) { resolve(data); } } catch (reason) { reject(reason); } }; thenable.then(retry, retry); return serializeByValueID(lazyId); } else { // In the future we could consider serializing this as an error // that throws on the server instead. reject(x); return null; } } finally { pendingParts--; } } } // $FlowFixMe[method-unbinding] if (typeof value.then === 'function') { // 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. if (formData === null) { // Upgrade to use FormData to allow us to stream this value. formData = new FormData(); } pendingParts++; const promiseId = nextPartId++; const thenable = value; thenable.then(partValue => { try { const partJSON = serializeModel(partValue, promiseId); // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above. const data = formData; data.append(formFieldPrefix + promiseId, partJSON); pendingParts--; if (pendingParts === 0) { resolve(data); } } catch (reason) { reject(reason); } }, // In the future we could consider serializing this as an error // that throws on the server instead. reject); return serializePromiseID(promiseId); } const existingReference = writtenObjects.get(value); if (existingReference !== 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 { // We've already emitted this as an outlined object, so we can // just refer to that by its existing ID. return existingReference; } } else if (key.indexOf(':') === -1) { // TODO: If the property name contains a colon, we don't dedupe. Escape instead. const parentReference = writtenObjects.get(parent); if (parentReference !== undefined) { // If the parent has a reference, we can refer to this object indirectly // through the property name inside that parent. const reference = parentReference + ':' + key; writtenObjects.set(value, reference); if (temporaryReferences !== undefined) { // Store this object so that the server can refer to it later in responses. writeTemporaryReference(temporaryReferences, reference, value); } } } if (isArray(value)) { // $FlowFixMe[incompatible-return] return value; } // TODO: Should we the Object.prototype.toString.call() to test for cross-realm objects? if (value instanceof FormData) { if (formData === null) { // Upgrade to use FormData to allow us to use rich objects as its values. formData = new FormData(); } const data = formData; const refId = nextPartId++; // Copy all the form fields with a prefix for this reference. // These must come first in the form order because we assume that all the // fields are available before this is referenced. const prefix = formFieldPrefix + refId + '_'; // $FlowFixMe[prop-missing]: FormData has forEach. value.forEach((originalValue, originalKey) => { // $FlowFixMe[incompatible-call] data.append(prefix + originalKey, originalValue); }); return serializeFormDataReference(refId); } if (value instanceof Map) { const mapId = nextPartId++; const partJSON = serializeModel(Array.from(value), mapId); if (formData === null) { formData = new FormData(); } formData.append(formFieldPrefix + mapId, partJSON); return serializeMapID(mapId); } if (value instanceof Set) { const setId = nextPartId++; const partJSON = serializeModel(Array.from(value), setId); if (formData === null) { formData = new FormData(); } formData.append(formFieldPrefix + setId, partJSON); return serializeSetID(setId); } if (value instanceof ArrayBuffer) { const blob = new Blob([value]); const blobId = nextPartId++; if (formData === null) { formData = new FormData(); } formData.append(formFieldPrefix + blobId, blob); return '$' + 'A' + blobId.toString(16); } if (value instanceof Int8Array) { // char return serializeTypedArray('O', value); } if (value instanceof Uint8Array) { // unsigned char return serializeTypedArray('o', value); } if (value instanceof Uint8ClampedArray) { // unsigned clamped char return serializeTypedArray('U', value); } if (value instanceof Int16Array) { // sort return serializeTypedArray('S', value); } if (value instanceof Uint16Array) { // unsigned short return serializeTypedArray('s', value); } if (value instanceof Int32Array) { // long return serializeTypedArray('L', value); } if (value instanceof Uint32Array) { // unsigned long return serializeTypedArray('l', value); } if (value instanceof Float32Array) { // float return serializeTypedArray('G', value); } if (value instanceof Float64Array) { // double return serializeTypedArray('g', value); } if (value instanceof BigInt64Array) { // number return serializeTypedArray('M', value); } if (value instanceof BigUint64Array) { // unsigned number // We use "m" instead of "n" since JSON can start with "null" return serializeTypedArray('m', value); } if (value instanceof DataView) { return serializeTypedArray('V', value); } // TODO: Blob is not available in old Node/browsers. Remove the typeof check later. if (typeof Blob === 'function' && value instanceof Blob) { if (formData === null) { formData = new FormData(); } const blobId = nextPartId++; formData.append(formFieldPrefix + blobId, value); return serializeBlobID(blobId); } const iteratorFn = getIteratorFn(value); if (iteratorFn) { const iterator = iteratorFn.call(value); if (iterator === value) { // Iterator, not Iterable const iteratorId = nextPartId++; const partJSON = serializeModel(Array.from(iterator), iteratorId); if (formData === null) { formData = new FormData(); } formData.append(formFieldPrefix + iteratorId, partJSON); return serializeIteratorID(iteratorId); } return Array.from(iterator); } // TODO: ReadableStream is not available in old Node. Remove the typeof check later. if (typeof ReadableStream === 'function' && value instanceof ReadableStream) { return serializeReadableStream(value); } const getAsyncIterator = value[ASYNC_ITERATOR]; if (typeof getAsyncIterator === 'function') { // We treat AsyncIterables as a Fragment and as such we might need to key them. return serializeAsyncIterable(value, getAsyncIterator.call(value)); } // Verify that this is a simple plain object. const proto = getPrototypeOf(value); if (proto !== ObjectPrototype && (proto === null || getPrototypeOf(proto) !== null)) { if (temporaryReferences === undefined) { throw new Error('Only plain objects, and a few built-ins, can be passed to Server Functions. ' + 'Classes or null prototypes are not supported.' + ('')); } // We will have written this object to the temporary reference set above // so we can replace it with a marker to refer to this slot later. return serializeTemporaryReferenceMarker(); } // $FlowFixMe[incompatible-return] return value; } if (typeof value === 'string') { // TODO: Maybe too clever. If we support URL there's no similar trick. if (value[value.length - 1] === 'Z') { // Possibly a Date, whose toJSON automatically calls toISOString // $FlowFixMe[incompatible-use] const originalValue = parent[key]; if (originalValue instanceof Date) { return serializeDateFromDateJSON(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') { const referenceClosure = knownServerReferences.get(value); if (referenceClosure !== undefined) { const id = referenceClosure.id, bound = referenceClosure.bound; const referenceClosureJSON = JSON.stringify({ id, bound }, resolveToJSON); if (formData === null) { // Upgrade to use FormData to allow us to stream this value. formData = new FormData(); } // The reference to this function came from the same client so we can pass it back. const refId = nextPartId++; formData.set(formFieldPrefix + refId, referenceClosureJSON); return serializeServerReferenceID(refId); } if (temporaryReferences !== undefined && key.indexOf(':') === -1) { // TODO: If the property name contains a colon, we don't dedupe. Escape instead. const parentReference = writtenObjects.get(parent); if (parentReference !== undefined) { // If the parent has a reference, we can refer to this object indirectly // through the property name inside that parent. const reference = parentReference + ':' + key; // Store this object so that the server can refer to it later in responses. writeTemporaryReference(temporaryReferences, reference, value); return serializeTemporaryReferenceMarker(); } } throw new Error('Client Functions cannot be passed directly to Server Functions. ' + 'Only Functions passed from the Server can be passed back again.'); } if (typeof value === 'symbol') { if (temporaryReferences !== undefined && key.indexOf(':') === -1) { // TODO: If the property name contains a colon, we don't dedupe. Escape instead. const parentReference = writtenObjects.get(parent); if (parentReference !== undefined) { // If the parent has a reference, we can refer to this object indirectly // through the property name inside that parent. const reference = parentReference + ':' + key; // Store this object so that the server can refer to it later in responses. writeTemporaryReference(temporaryReferences, reference, value); return serializeTemporaryReferenceMarker(); } } throw new Error('Symbols cannot be passed to a Server Function without a ' + 'temporary reference set. Pass a TemporaryReferenceSet to the options.' + ('')); } if (typeof value === 'bigint') { return serializeBigInt(value); } throw new Error("Type " + typeof value + " is not supported as an argument to a Server Function."); } function serializeModel(model, id) { if (typeof model === 'object' && model !== null) { const reference = serializeByValueID(id); writtenObjects.set(model, reference); if (temporaryReferences !== undefined) { // Store this object so that the server can refer to it later in responses. writeTemporaryReference(temporaryReferences, reference, model); } } modelRoot = model; // $FlowFixMe[incompatible-return] it's not going to be undefined because we'll encode it. return JSON.stringify(model, resolveToJSON); } function abort(reason) { if (pendingParts > 0) { pendingParts = 0; // Don't resolve again later. // Resolve with what we have so far, which may have holes at this point. // They'll error when the stream completes on the server. if (formData === null) { resolve(json); } else { resolve(formData); } } } const json = serializeModel(root, 0); if (formData === null) { // If it's a simple data structure, we just use plain JSON. resolve(json); } else { // Otherwise, we use FormData to let us stream in the result. formData.set(formFieldPrefix + '0', json); if (pendingParts === 0) { // $FlowFixMe[incompatible-call] this has already been refined. resolve(formData); } } return abort; } function registerBoundServerReference(reference, id, bound, encodeFormAction) { if (knownServerReferences.has(reference)) { return; } knownServerReferences.set(reference, { id, originalBind: reference.bind, bound }); } function registerServerReference(reference, id, encodeFormAction) { registerBoundServerReference(reference, id, null); return reference; } function createBoundServerReference(metaData, callServer, encodeFormAction, findSourceMapURL // DEV-only ) { const id = metaData.id; const bound = metaData.bound; let action = function () { // $FlowFixMe[method-unbinding] const args = Array.prototype.slice.call(arguments); const p = bound; if (!p) { return callServer(id, args); } if (p.status === 'fulfilled') { const boundArgs = p.value; return callServer(id, boundArgs.concat(args)); } // Since this is a fake Promise whose .then doesn't chain, we have to wrap it. // TODO: Remove the wrapper once that's fixed. return Promise.resolve(p).then(function (boundArgs) { return callServer(id, boundArgs.concat(args)); }); }; registerBoundServerReference(action, id, bound); return action; } function createServerReference(id, callServer, encodeFormAction, findSourceMapURL, // DEV-only functionName) { let action = function () { // $FlowFixMe[method-unbinding] const args = Array.prototype.slice.call(arguments); return callServer(id, args); }; registerBoundServerReference(action, id, null); return action; } const ROW_ID = 0; const ROW_TAG = 1; const ROW_LENGTH = 2; const ROW_CHUNK_BY_NEWLINE = 3; const ROW_CHUNK_BY_LENGTH = 4; const PENDING = 'pending'; const BLOCKED = 'blocked'; const RESOLVED_MODEL = 'resolved_model'; const RESOLVED_MODULE = 'resolved_module'; const INITIALIZED = 'fulfilled'; const ERRORED = 'rejected'; const HALTED = 'halted'; // DEV-only. Means it never resolves even if connection closes. // $FlowFixMe[missing-this-annot] function ReactPromise(status, value, reason) { this.status = status; this.value = value; this.reason = reason; } // We subclass Promise.prototype so that we get other methods like .catch ReactPromise.prototype = Object.create(Promise.prototype); // TODO: This doesn't return a new Promise chain unlike the real .then ReactPromise.prototype.then = function (resolve, reject) { const chunk = this; // If we have resolved content, we try to initialize it first which // might put us back into one of the other states. switch (chunk.status) { case RESOLVED_MODEL: initializeModelChunk(chunk); break; case RESOLVED_MODULE: initializeModuleChunk(chunk); break; } // The status might have changed after initialization. switch (chunk.status) { case INITIALIZED: if (typeof resolve === 'function') { resolve(chunk.value); } break; case PENDING: case BLOCKED: if (typeof resolve === 'function') { if (chunk.value === null) { chunk.value = []; } chunk.value.push(resolve); } if (typeof reject === 'function') { if (chunk.reason === null) { chunk.reason = []; } chunk.reason.push(reject); } break; case HALTED: { break; } default: if (typeof reject === 'function') { reject(chunk.reason); } break; } }; function unwrapWeakResponse(weakResponse) { { return weakResponse; // In prod we just use the real Response directly. } } function getWeakResponse(response) { { return response; // In prod we just use the real Response directly. } } function readChunk(chunk) { // If we have resolved content, we try to initialize it first which // might put us back into one of the other states. switch (chunk.status) { case RESOLVED_MODEL: initializeModelChunk(chunk); break; case RESOLVED_MODULE: initializeModuleChunk(chunk); break; } // The status might have changed after initialization. switch (chunk.status) { case INITIALIZED: return chunk.value; case PENDING: case BLOCKED: case HALTED: // eslint-disable-next-line no-throw-literal throw chunk; default: throw chunk.reason; } } function getRoot(weakResponse) { const response = unwrapWeakResponse(weakResponse); const chunk = getChunk(response, 0); return chunk; } function createPendingChunk(response) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(PENDING, null, null); } function createBlockedChunk(response) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(BLOCKED, null, null); } function createErrorChunk(response, error) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(ERRORED, null, error); } function wakeChunk(listeners, value) { for (let i = 0; i < listeners.length; i++) { const listener = listeners[i]; if (typeof listener === 'function') { listener(value); } else { fulfillReference(listener, value); } } } function rejectChunk(listeners, error) { for (let i = 0; i < listeners.length; i++) { const listener = listeners[i]; if (typeof listener === 'function') { listener(error); } else { rejectReference(listener, error); } } } function resolveBlockedCycle(resolvedChunk, reference) { const referencedChunk = reference.handler.chunk; if (referencedChunk === null) { return null; } if (referencedChunk === resolvedChunk) { // We found the cycle. We can resolve the blocked cycle now. return reference.handler; } const resolveListeners = referencedChunk.value; if (resolveListeners !== null) { for (let i = 0; i < resolveListeners.length; i++) { const listener = resolveListeners[i]; if (typeof listener !== 'function') { const foundHandler = resolveBlockedCycle(resolvedChunk, listener); if (foundHandler !== null) { return foundHandler; } } } } return null; } function wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners) { switch (chunk.status) { case INITIALIZED: wakeChunk(resolveListeners, chunk.value); break; case BLOCKED: // It is possible that we're blocked on our own chunk if it's a cycle. // Before adding back the listeners to the chunk, let's check if it would // result in a cycle. for (let i = 0; i < resolveListeners.length; i++) { const listener = resolveListeners[i]; if (typeof listener !== 'function') { const reference = listener; const cyclicHandler = resolveBlockedCycle(chunk, reference); if (cyclicHandler !== null) { // This reference points back to this chunk. We can resolve the cycle by // using the value from that handler. fulfillReference(reference, cyclicHandler.value); resolveListeners.splice(i, 1); i--; if (rejectListeners !== null) { const rejectionIdx = rejectListeners.indexOf(reference); if (rejectionIdx !== -1) { rejectListeners.splice(rejectionIdx, 1); } } } } } // Fallthrough case PENDING: if (chunk.value) { for (let i = 0; i < resolveListeners.length; i++) { chunk.value.push(resolveListeners[i]); } } else { chunk.value = resolveListeners; } if (chunk.reason) { if (rejectListeners) { for (let i = 0; i < rejectListeners.length; i++) { chunk.reason.push(rejectListeners[i]); } } } else { chunk.reason = rejectListeners; } break; case ERRORED: if (rejectListeners) { rejectChunk(rejectListeners, chunk.reason); } break; } } function triggerErrorOnChunk(response, chunk, error) { if (chunk.status !== PENDING && chunk.status !== BLOCKED) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. const streamChunk = chunk; const controller = streamChunk.reason; // $FlowFixMe[incompatible-call]: The error method should accept mixed. controller.error(error); return; } const listeners = chunk.reason; const erroredChunk = chunk; erroredChunk.status = ERRORED; erroredChunk.reason = error; if (listeners !== null) { rejectChunk(listeners, error); } } function createResolvedModelChunk(response, value) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(RESOLVED_MODEL, value, response); } function createResolvedModuleChunk(response, value) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(RESOLVED_MODULE, value, null); } function createInitializedTextChunk(response, value) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(INITIALIZED, value, null); } function createInitializedBufferChunk(response, value) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(INITIALIZED, value, null); } function createInitializedIteratorResultChunk(response, value, done) { // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(INITIALIZED, { done: done, value: value }, null); } function createInitializedStreamChunk(response, value, controller) { // We use the reason field to stash the controller since we already have that // field. It's a bit of a hack but efficient. // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(INITIALIZED, value, controller); } function createResolvedIteratorResultChunk(response, value, done) { // To reuse code as much code as possible we add the wrapper element as part of the JSON. const iteratorResultJSON = (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}'; // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, response); } function resolveIteratorResultChunk(response, chunk, value, done) { // To reuse code as much code as possible we add the wrapper element as part of the JSON. const iteratorResultJSON = (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}'; resolveModelChunk(response, chunk, iteratorResultJSON); } function resolveModelChunk(response, chunk, value) { if (chunk.status !== PENDING) { // If we get more data to an already resolved ID, we assume that it's // a stream chunk since any other row shouldn't have more than one entry. const streamChunk = chunk; const controller = streamChunk.reason; controller.enqueueModel(value); return; } const resolveListeners = chunk.value; const rejectListeners = chunk.reason; const resolvedChunk = chunk; resolvedChunk.status = RESOLVED_MODEL; resolvedChunk.value = value; resolvedChunk.reason = response; if (resolveListeners !== null) { // This is unfortunate that we're reading this eagerly if // we already have listeners attached since they might no // longer be rendered or might not be the highest pri. initializeModelChunk(resolvedChunk); // The status might have changed after initialization. wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners); } } function resolveModuleChunk(response, chunk, value) { if (chunk.status !== PENDING && chunk.status !== BLOCKED) { // We already resolved. We didn't expect to see this. return; } const resolveListeners = chunk.value; const rejectListeners = chunk.reason; const resolvedChunk = chunk; resolvedChunk.status = RESOLVED_MODULE; resolvedChunk.value = value; if (resolveListeners !== null) { initializeModuleChunk(resolvedChunk); wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners); } } let initializingHandler = null; function initializeModelChunk(chunk) { const prevHandler = initializingHandler; initializingHandler = null; const resolvedModel = chunk.value; const response = chunk.reason; // We go to the BLOCKED state until we've fully resolved this. // We do this before parsing in case we try to initialize the same chunk // while parsing the model. Such as in a cyclic reference. const cyclicChunk = chunk; cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; try { const value = parseModel(response, resolvedModel); // Invoke any listeners added while resolving this model. I.e. cyclic // references. This may or may not fully resolve the model depending on // if they were blocked. const resolveListeners = cyclicChunk.value; if (resolveListeners !== null) { cyclicChunk.value = null; cyclicChunk.reason = null; wakeChunk(resolveListeners, value); } if (initializingHandler !== null) { if (initializingHandler.errored) { throw initializingHandler.reason; } if (initializingHandler.deps > 0) { // We discovered new dependencies on modules that are not yet resolved. // We have to keep the BLOCKED state until they're resolved. initializingHandler.value = value; initializingHandler.chunk = cyclicChunk; return; } } const initializedChunk = chunk; initializedChunk.status = INITIALIZED; initializedChunk.value = value; } catch (error) { const erroredChunk = chunk; erroredChunk.status = ERRORED; erroredChunk.reason = error; } finally { initializingHandler = prevHandler; } } function initializeModuleChunk(chunk) { try { const value = requireModule(chunk.value); const initializedChunk = chunk; initializedChunk.status = INITIALIZED; initializedChunk.value = value; } catch (error) { const erroredChunk = chunk; erroredChunk.status = ERRORED; erroredChunk.reason = error; } } // Report that any missing chunks in the model is now going to throw this // error upon read. Also notify any pending promises. function reportGlobalError(weakResponse, error) { const response = unwrapWeakResponse(weakResponse); response._closed = true; response._closedReason = error; response._chunks.forEach(chunk => { // If this chunk was already resolved or errored, it won't // trigger an error but if it wasn't then we need to // because we won't be getting any new data to resolve it. if (chunk.status === PENDING) { triggerErrorOnChunk(response, chunk, error); } }); } function createElement(response, type, key, props, owner, // DEV-only stack, // DEV-only validated // DEV-only ) { let element; { element = { // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, type, key, ref: null, props }; } if (initializingHandler !== null) { const handler = initializingHandler; // We pop the stack to the previous outer handler before leaving the Element. // This is effectively the complete phase. initializingHandler = handler.parent; if (handler.errored) { // Something errored inside this Element's props. We can turn this Element // into a Lazy so that we can still render up until that Lazy is rendered. const erroredChunk = createErrorChunk(response, handler.reason); return createLazyChunkWrapper(erroredChunk); } if (handler.deps > 0) { // We have blocked references inside this Element but we can turn this into // a Lazy node referencing this Element to let everything around it proceed. const blockedChunk = createBlockedChunk(); handler.value = element; handler.chunk = blockedChunk; return createLazyChunkWrapper(blockedChunk); } } return element; } function createLazyChunkWrapper(chunk) { const lazyType = { $$typeof: REACT_LAZY_TYPE, _payload: chunk, _init: readChunk }; return lazyType; } function getChunk(response, id) { const chunks = response._chunks; let chunk = chunks.get(id); if (!chunk) { if (response._closed) { // We have already errored the response and we're not going to get // anything more streaming in so this will immediately error. chunk = createErrorChunk(response, response._closedReason); } else { chunk = createPendingChunk(); } chunks.set(id, chunk); } return chunk; } function fulfillReference(reference, value) { const response = reference.response, handler = reference.handler, parentObject = reference.parentObject, key = reference.key, map = reference.map, path = reference.path; for (let i = 1; i < path.length; i++) { while (value.$$typeof === REACT_LAZY_TYPE) { // We never expect to see a Lazy node on this path because we encode those as // separate models. This must mean that we have inserted an extra lazy node // e.g. to replace a blocked element. We must instead look for it inside. const referencedChunk = value._payload; if (referencedChunk === handler.chunk) { // This is a reference to the thing we're currently blocking. We can peak // inside of it to get the value. value = handler.value; continue; } else { switch (referencedChunk.status) { case RESOLVED_MODEL: initializeModelChunk(referencedChunk); break; case RESOLVED_MODULE: initializeModuleChunk(referencedChunk); break; } switch (referencedChunk.status) { case INITIALIZED: { value = referencedChunk.value; continue; } case BLOCKED: { // It is possible that we're blocked on our own chunk if it's a cycle. // Before adding the listener to the inner chunk, let's check if it would // result in a cycle. const cyclicHandler = resolveBlockedCycle(referencedChunk, reference); if (cyclicHandler !== null) { // This reference points back to this chunk. We can resolve the cycle by