Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b910fc15e3 | ||
|
|
dd0519822a | ||
|
|
0eeded69c1 | ||
|
|
a4d338a6c6 | ||
|
|
053df4e856 | ||
|
|
b180cb2b25 | ||
|
|
581fdc550a |
@@ -331,6 +331,7 @@ module.exports = {
|
||||
'packages/react-server-dom-turbopack/**/*.js',
|
||||
'packages/react-server-dom-parcel/**/*.js',
|
||||
'packages/react-server-dom-fb/**/*.js',
|
||||
'packages/react-server-dom-unbundled/**/*.js',
|
||||
'packages/react-test-renderer/**/*.js',
|
||||
'packages/react-debug-tools/**/*.js',
|
||||
'packages/react-devtools-extensions/**/*.js',
|
||||
|
||||
88
.github/workflows/runtime_build_and_test.yml
vendored
88
.github/workflows/runtime_build_and_test.yml
vendored
@@ -3,6 +3,10 @@ name: (Runtime) Build and Test
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
# To get CI for backport releases.
|
||||
# This will duplicate CI for releases from main which is acceptable
|
||||
- "v*"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
@@ -41,7 +45,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
lookup-only: true
|
||||
- uses: actions/setup-node@v4
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
@@ -55,10 +59,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Save cache
|
||||
@@ -67,7 +69,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
runtime_compiler_node_modules_cache:
|
||||
name: Cache Runtime, Compiler node_modules
|
||||
@@ -82,7 +84,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
lookup-only: true
|
||||
- uses: actions/setup-node@v4
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
@@ -98,10 +100,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
|
||||
# ----- FLOW -----
|
||||
discover_flow_inline_configs:
|
||||
@@ -154,10 +154,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -184,10 +182,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -216,7 +212,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -274,10 +270,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -306,7 +300,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
- name: Install runtime dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
@@ -349,10 +343,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -440,10 +432,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -483,10 +473,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -548,10 +536,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -588,10 +574,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -740,10 +724,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -802,10 +784,8 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
//
|
||||
// The @latest channel uses the version as-is, e.g.:
|
||||
//
|
||||
// 19.2.0
|
||||
// 19.2.2
|
||||
//
|
||||
// The @canary channel appends additional information, with the scheme
|
||||
// <version>-<label>-<commit_sha>, e.g.:
|
||||
//
|
||||
// 19.2.0-canary-a1c2d3e4
|
||||
// 19.2.2-canary-a1c2d3e4
|
||||
//
|
||||
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
|
||||
//
|
||||
// 0.0.0-experimental-241c4467e-20200129
|
||||
|
||||
const ReactVersion = '19.2.0';
|
||||
const ReactVersion = '19.2.2';
|
||||
|
||||
// The label used by the @canary channel. Represents the upcoming release's
|
||||
// stability. Most of the time, this will be "canary", but we may temporarily
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
load as reactLoad,
|
||||
getSource as getSourceImpl,
|
||||
transformSource as reactTransformSource,
|
||||
} from 'react-server-dom-webpack/node-loader';
|
||||
} from 'react-server-dom-unbundled/node-loader';
|
||||
|
||||
export {resolve};
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const http = require('http');
|
||||
const React = require('react');
|
||||
|
||||
const {renderToPipeableStream} = require('react-dom/server');
|
||||
const {createFromNodeStream} = require('react-server-dom-webpack/client');
|
||||
const {createFromNodeStream} = require('react-server-dom-unbundled/client');
|
||||
const {PassThrough} = require('stream');
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
|
||||
const register = require('react-server-dom-webpack/node-register');
|
||||
const register = require('react-server-dom-unbundled/node-register');
|
||||
// TODO: This seems to have no effect anymore. Remove?
|
||||
register();
|
||||
|
||||
const babelRegister = require('@babel/register');
|
||||
@@ -76,7 +77,7 @@ function getDebugChannel(req) {
|
||||
|
||||
async function renderApp(res, returnValue, formState, noCache, debugChannel) {
|
||||
const {renderToPipeableStream} = await import(
|
||||
'react-server-dom-webpack/server'
|
||||
'react-server-dom-unbundled/server'
|
||||
);
|
||||
// const m = require('../src/App.js');
|
||||
const m = await import('../src/App.js');
|
||||
@@ -134,7 +135,7 @@ async function renderApp(res, returnValue, formState, noCache, debugChannel) {
|
||||
|
||||
async function prerenderApp(res, returnValue, formState, noCache) {
|
||||
const {prerenderToNodeStream} = await import(
|
||||
'react-server-dom-webpack/static'
|
||||
'react-server-dom-unbundled/static'
|
||||
);
|
||||
// const m = require('../src/App.js');
|
||||
const m = await import('../src/App.js');
|
||||
@@ -202,7 +203,7 @@ app.get('/', async function (req, res) {
|
||||
app.post('/', bodyParser.text(), async function (req, res) {
|
||||
const noCache = req.headers['cache-control'] === 'no-cache';
|
||||
const {decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState} =
|
||||
await import('react-server-dom-webpack/server');
|
||||
await import('react-server-dom-unbundled/server');
|
||||
const serverReference = req.get('rsc-action');
|
||||
if (serverReference) {
|
||||
// This is the client-side case
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import {renderToReadableStream} from 'react-server-dom-webpack/server';
|
||||
import {renderToReadableStream} from 'react-server-dom-unbundled/server';
|
||||
import {createFromReadableStream} from 'react-server-dom-webpack/client';
|
||||
import {PassThrough, Readable} from 'stream';
|
||||
|
||||
|
||||
@@ -123,10 +123,10 @@
|
||||
"scripts": {
|
||||
"prebuild": "./scripts/react-compiler/link-compiler.sh",
|
||||
"build": "node ./scripts/rollup/build-all-release-channels.js",
|
||||
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE",
|
||||
"build-for-devtools": "cross-env yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE --release-channel=experimental",
|
||||
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
|
||||
"build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
|
||||
"build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",
|
||||
"build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/,react-server-dom-unbundled/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",
|
||||
"build-for-vt-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react-dom/index,react-dom/client,react-dom/server,react-dom-server.node,react-dom-server-legacy.node,scheduler --type=NODE_DEV && mv ./build/node_modules ./build/oss-experimental",
|
||||
"flow-typed-install": "yarn flow-typed install --skip --skipFlowRestart --ignore-deps=dev",
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,7 +27,7 @@
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0"
|
||||
"react": "^19.2.2"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
279
packages/react-client/src/ReactFlightClient.js
vendored
279
packages/react-client/src/ReactFlightClient.js
vendored
@@ -623,6 +623,22 @@ function wakeChunkIfInitialized<T>(
|
||||
rejectListeners.splice(rejectionIdx, 1);
|
||||
}
|
||||
}
|
||||
// The status might have changed after fulfilling the reference.
|
||||
switch ((chunk: SomeChunk<T>).status) {
|
||||
case INITIALIZED:
|
||||
const initializedChunk: InitializedChunk<T> = (chunk: any);
|
||||
wakeChunk(
|
||||
resolveListeners,
|
||||
initializedChunk.value,
|
||||
initializedChunk,
|
||||
);
|
||||
return;
|
||||
case ERRORED:
|
||||
if (rejectListeners !== null) {
|
||||
rejectChunk(rejectListeners, chunk.reason);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -834,6 +850,7 @@ function resolveModuleChunk<T>(
|
||||
const resolvedChunk: ResolvedModuleChunk<T> = (chunk: any);
|
||||
resolvedChunk.status = RESOLVED_MODULE;
|
||||
resolvedChunk.value = value;
|
||||
resolvedChunk.reason = null;
|
||||
if (__DEV__) {
|
||||
const debugInfo = getModuleDebugInfo(value);
|
||||
if (debugInfo !== null) {
|
||||
@@ -1055,6 +1072,8 @@ export function reportGlobalError(
|
||||
// because we won't be getting any new data to resolve it.
|
||||
if (chunk.status === PENDING) {
|
||||
triggerErrorOnChunk(response, chunk, error);
|
||||
} else if (chunk.status === INITIALIZED && chunk.reason !== null) {
|
||||
chunk.reason.error(error);
|
||||
}
|
||||
});
|
||||
if (__DEV__) {
|
||||
@@ -1402,15 +1421,91 @@ function fulfillReference(
|
||||
): void {
|
||||
const {response, handler, parentObject, key, map, path} = reference;
|
||||
|
||||
for (let i = 1; i < path.length; i++) {
|
||||
try {
|
||||
for (let i = 1; i < path.length; i++) {
|
||||
while (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
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: SomeChunk<any> = 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
|
||||
// using the value from that handler.
|
||||
value = cyclicHandler.value;
|
||||
continue;
|
||||
}
|
||||
// Fallthrough
|
||||
}
|
||||
case PENDING: {
|
||||
// If we're not yet initialized we need to skip what we've already drilled
|
||||
// through and then wait for the next value to become available.
|
||||
path.splice(0, i - 1);
|
||||
// Add "listener" to our new chunk dependency.
|
||||
if (referencedChunk.value === null) {
|
||||
referencedChunk.value = [reference];
|
||||
} else {
|
||||
referencedChunk.value.push(reference);
|
||||
}
|
||||
if (referencedChunk.reason === null) {
|
||||
referencedChunk.reason = [reference];
|
||||
} else {
|
||||
referencedChunk.reason.push(reference);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HALTED: {
|
||||
// Do nothing. We couldn't fulfill.
|
||||
// TODO: Mark downstreams as halted too.
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
rejectReference(reference, referencedChunk.reason);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
value = value[path[i]];
|
||||
}
|
||||
|
||||
while (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
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.
|
||||
// If what we're referencing is a Lazy it must be because we inserted one as a virtual node
|
||||
// while it was blocked by other data. If it's no longer blocked, we can unwrap it.
|
||||
const referencedChunk: SomeChunk<any> = value._payload;
|
||||
if (referencedChunk === handler.chunk) {
|
||||
// This is a reference to the thing we're currently blocking. We can peak
|
||||
@@ -1431,128 +1526,57 @@ function fulfillReference(
|
||||
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
|
||||
// using the value from that handler.
|
||||
value = cyclicHandler.value;
|
||||
continue;
|
||||
}
|
||||
// Fallthrough
|
||||
}
|
||||
case PENDING: {
|
||||
// If we're not yet initialized we need to skip what we've already drilled
|
||||
// through and then wait for the next value to become available.
|
||||
path.splice(0, i - 1);
|
||||
// Add "listener" to our new chunk dependency.
|
||||
if (referencedChunk.value === null) {
|
||||
referencedChunk.value = [reference];
|
||||
} else {
|
||||
referencedChunk.value.push(reference);
|
||||
}
|
||||
if (referencedChunk.reason === null) {
|
||||
referencedChunk.reason = [reference];
|
||||
} else {
|
||||
referencedChunk.reason.push(reference);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HALTED: {
|
||||
// Do nothing. We couldn't fulfill.
|
||||
// TODO: Mark downstreams as halted too.
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
rejectReference(reference, referencedChunk.reason);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
value = value[path[i]];
|
||||
}
|
||||
|
||||
while (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
value.$$typeof === REACT_LAZY_TYPE
|
||||
) {
|
||||
// If what we're referencing is a Lazy it must be because we inserted one as a virtual node
|
||||
// while it was blocked by other data. If it's no longer blocked, we can unwrap it.
|
||||
const referencedChunk: SomeChunk<any> = 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);
|
||||
const mappedValue = map(response, value, parentObject, key);
|
||||
parentObject[key] = mappedValue;
|
||||
|
||||
// If this is the root object for a model reference, where `handler.value`
|
||||
// is a stale `null`, the resolved value can be used directly.
|
||||
if (key === '' && handler.value === null) {
|
||||
handler.value = mappedValue;
|
||||
}
|
||||
|
||||
// If the parent object is an unparsed React element tuple, we also need to
|
||||
// update the props and owner of the parsed element object (i.e.
|
||||
// handler.value).
|
||||
if (
|
||||
parentObject[0] === REACT_ELEMENT_TYPE &&
|
||||
typeof handler.value === 'object' &&
|
||||
handler.value !== null &&
|
||||
handler.value.$$typeof === REACT_ELEMENT_TYPE
|
||||
) {
|
||||
const element: any = handler.value;
|
||||
switch (key) {
|
||||
case '3':
|
||||
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
|
||||
element.props = mappedValue;
|
||||
break;
|
||||
case RESOLVED_MODULE:
|
||||
initializeModuleChunk(referencedChunk);
|
||||
case '4':
|
||||
// This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.
|
||||
if (__DEV__) {
|
||||
element._owner = mappedValue;
|
||||
}
|
||||
break;
|
||||
case '5':
|
||||
// This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.
|
||||
if (__DEV__) {
|
||||
element._debugStack = mappedValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
|
||||
break;
|
||||
}
|
||||
switch (referencedChunk.status) {
|
||||
case INITIALIZED: {
|
||||
value = referencedChunk.value;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (__DEV__ && !reference.isDebug) {
|
||||
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const mappedValue = map(response, value, parentObject, key);
|
||||
parentObject[key] = mappedValue;
|
||||
|
||||
// If this is the root object for a model reference, where `handler.value`
|
||||
// is a stale `null`, the resolved value can be used directly.
|
||||
if (key === '' && handler.value === null) {
|
||||
handler.value = mappedValue;
|
||||
}
|
||||
|
||||
// If the parent object is an unparsed React element tuple, we also need to
|
||||
// update the props and owner of the parsed element object (i.e.
|
||||
// handler.value).
|
||||
if (
|
||||
parentObject[0] === REACT_ELEMENT_TYPE &&
|
||||
typeof handler.value === 'object' &&
|
||||
handler.value !== null &&
|
||||
handler.value.$$typeof === REACT_ELEMENT_TYPE
|
||||
) {
|
||||
const element: any = handler.value;
|
||||
switch (key) {
|
||||
case '3':
|
||||
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
|
||||
element.props = mappedValue;
|
||||
break;
|
||||
case '4':
|
||||
// This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.
|
||||
if (__DEV__) {
|
||||
element._owner = mappedValue;
|
||||
}
|
||||
break;
|
||||
case '5':
|
||||
// This path doesn't call transferReferencedDebugInfo because this reference is to a debug chunk.
|
||||
if (__DEV__) {
|
||||
element._debugStack = mappedValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
|
||||
break;
|
||||
}
|
||||
} else if (__DEV__ && !reference.isDebug) {
|
||||
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
|
||||
} catch (error) {
|
||||
rejectReference(reference, error);
|
||||
return;
|
||||
}
|
||||
|
||||
handler.deps--;
|
||||
@@ -1816,6 +1840,7 @@ function loadServerReference<A: Iterable<any>, T>(
|
||||
const initializedChunk: InitializedChunk<T> = (chunk: any);
|
||||
initializedChunk.status = INITIALIZED;
|
||||
initializedChunk.value = handler.value;
|
||||
initializedChunk.reason = null;
|
||||
if (resolveListeners !== null) {
|
||||
wakeChunk(resolveListeners, handler.value, initializedChunk);
|
||||
}
|
||||
@@ -2289,7 +2314,7 @@ function parseModelString(
|
||||
// Symbol
|
||||
return Symbol.for(value.slice(2));
|
||||
}
|
||||
case 'F': {
|
||||
case 'h': {
|
||||
// Server Reference
|
||||
const ref = value.slice(2);
|
||||
return getOutlinedModel(
|
||||
@@ -3044,6 +3069,7 @@ function startReadableStream<T>(
|
||||
streamState: StreamState,
|
||||
): void {
|
||||
let controller: ReadableStreamController = (null: any);
|
||||
let closed = false;
|
||||
const stream = new ReadableStream({
|
||||
type: type,
|
||||
start(c) {
|
||||
@@ -3101,6 +3127,10 @@ function startReadableStream<T>(
|
||||
}
|
||||
},
|
||||
close(json: UninitializedModel): void {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
if (previousBlockedChunk === null) {
|
||||
controller.close();
|
||||
} else {
|
||||
@@ -3111,6 +3141,10 @@ function startReadableStream<T>(
|
||||
}
|
||||
},
|
||||
error(error: mixed): void {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
if (previousBlockedChunk === null) {
|
||||
// $FlowFixMe[incompatible-call]
|
||||
controller.error(error);
|
||||
@@ -3171,6 +3205,7 @@ function startAsyncIterable<T>(
|
||||
(chunk: any);
|
||||
initializedChunk.status = INITIALIZED;
|
||||
initializedChunk.value = {done: false, value: value};
|
||||
initializedChunk.reason = null;
|
||||
if (resolveListeners !== null) {
|
||||
wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners);
|
||||
}
|
||||
@@ -3195,6 +3230,9 @@ function startAsyncIterable<T>(
|
||||
nextWriteIndex++;
|
||||
},
|
||||
close(value: UninitializedModel): void {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
if (nextWriteIndex === buffer.length) {
|
||||
buffer[nextWriteIndex] = createResolvedIteratorResultChunk(
|
||||
@@ -3222,6 +3260,9 @@ function startAsyncIterable<T>(
|
||||
}
|
||||
},
|
||||
error(error: Error): void {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
if (nextWriteIndex === buffer.length) {
|
||||
buffer[nextWriteIndex] =
|
||||
|
||||
@@ -104,7 +104,7 @@ function serializePromiseID(id: number): string {
|
||||
}
|
||||
|
||||
function serializeServerReferenceID(id: number): string {
|
||||
return '$F' + id.toString(16);
|
||||
return '$h' + id.toString(16);
|
||||
}
|
||||
|
||||
function serializeTemporaryReferenceMarker(): string {
|
||||
@@ -112,7 +112,6 @@ function serializeTemporaryReferenceMarker(): string {
|
||||
}
|
||||
|
||||
function serializeFormDataReference(id: number): string {
|
||||
// Why K? F is "Function". D is "Date". What else?
|
||||
return '$K' + id.toString(16);
|
||||
}
|
||||
|
||||
@@ -474,8 +473,22 @@ export function processReply(
|
||||
}
|
||||
}
|
||||
|
||||
const existingReference = writtenObjects.get(value);
|
||||
|
||||
// $FlowFixMe[method-unbinding]
|
||||
if (typeof value.then === 'function') {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -484,11 +497,19 @@ export function processReply(
|
||||
}
|
||||
pendingParts++;
|
||||
const promiseId = nextPartId++;
|
||||
const promiseReference = serializePromiseID(promiseId);
|
||||
writtenObjects.set(value, promiseReference);
|
||||
const thenable: Thenable<any> = (value: any);
|
||||
thenable.then(
|
||||
partValue => {
|
||||
try {
|
||||
const partJSON = serializeModel(partValue, promiseId);
|
||||
const previousReference = writtenObjects.get(partValue);
|
||||
let partJSON;
|
||||
if (previousReference !== undefined) {
|
||||
partJSON = JSON.stringify(previousReference);
|
||||
} else {
|
||||
partJSON = serializeModel(partValue, promiseId);
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
|
||||
const data: FormData = formData;
|
||||
data.append(formFieldPrefix + promiseId, partJSON);
|
||||
@@ -504,10 +525,9 @@ export function processReply(
|
||||
// that throws on the server instead.
|
||||
reject,
|
||||
);
|
||||
return serializePromiseID(promiseId);
|
||||
return promiseReference;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export {default as rendererVersion} from 'shared/ReactVersion';
|
||||
export const rendererPackageName = 'react-server-dom-webpack';
|
||||
export const rendererPackageName = 'react-server-dom-unbundled';
|
||||
|
||||
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
|
||||
export * from 'react-client/src/ReactClientConsoleConfigServer';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
|
||||
export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode';
|
||||
export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer';
|
||||
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
|
||||
export const usedWithSSR = true;
|
||||
@@ -6,13 +6,13 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export {default as rendererVersion} from 'shared/ReactVersion';
|
||||
export const rendererPackageName = 'react-server-dom-webpack';
|
||||
|
||||
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
|
||||
export * from 'react-client/src/ReactClientConsoleConfigServer';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
|
||||
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
|
||||
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
|
||||
export const usedWithSSR = true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-dom-bindings",
|
||||
"description": "React implementation details for react-dom.",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0"
|
||||
"react": "^19.2.2"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-is",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"description": "Brand checking of React Elements.",
|
||||
"main": "index.js",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-markup",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"description": "React package generating embedded markup such as e-mails with support for Server Components.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0"
|
||||
"react": "^19.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-server-dom-esm",
|
||||
"description": "React Server Components bindings for DOM using ESM. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
|
||||
@@ -88,6 +88,12 @@ function bind(this: ServerReference<any>): any {
|
||||
return newFn;
|
||||
}
|
||||
|
||||
const serverReferenceToString = {
|
||||
value: () => 'function () { [omitted code] }',
|
||||
configurable: true,
|
||||
writable: true,
|
||||
};
|
||||
|
||||
export function registerServerReference<T: Function>(
|
||||
reference: T,
|
||||
id: string,
|
||||
@@ -111,12 +117,14 @@ export function registerServerReference<T: Function>(
|
||||
configurable: true,
|
||||
},
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
}
|
||||
: {
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
}) as PropertyDescriptorMap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -348,16 +348,23 @@ function decodeReplyFromBusboy<T>(
|
||||
// we queue any fields we receive until the previous file is done.
|
||||
queuedFields.push(name, value);
|
||||
} else {
|
||||
resolveField(response, name, value);
|
||||
try {
|
||||
resolveField(response, name, value);
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
|
||||
if (encoding.toLowerCase() === 'base64') {
|
||||
throw new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
busboyStream.destroy(
|
||||
new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
pendingFiles++;
|
||||
const file = resolveFileInfo(response, name, filename, mimeType);
|
||||
@@ -365,14 +372,18 @@ function decodeReplyFromBusboy<T>(
|
||||
resolveFileChunk(response, file, chunk);
|
||||
});
|
||||
value.on('end', () => {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
try {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-server-dom-parcel",
|
||||
"description": "React Server Components bindings for DOM using Parcel. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -79,7 +79,7 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react": "^19.2.2",
|
||||
"react-dom": "^19.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,12 @@ function bind(this: ServerReference<any>): any {
|
||||
return newFn;
|
||||
}
|
||||
|
||||
const serverReferenceToString = {
|
||||
value: () => 'function () { [omitted code] }',
|
||||
configurable: true,
|
||||
writable: true,
|
||||
};
|
||||
|
||||
export function registerServerReference<T>(
|
||||
reference: ServerReference<T>,
|
||||
id: string,
|
||||
@@ -118,12 +124,14 @@ export function registerServerReference<T>(
|
||||
configurable: true,
|
||||
},
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
}
|
||||
: {
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
}) as PropertyDescriptorMap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
} from '../shared/ReactFlightImportMetadata';
|
||||
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import hasOwnProperty from 'shared/hasOwnProperty';
|
||||
|
||||
export type ServerManifest = {
|
||||
[string]: Array<string>,
|
||||
};
|
||||
@@ -78,7 +80,10 @@ export function preloadModule<T>(
|
||||
|
||||
export function requireModule<T>(metadata: ClientReference<T>): T {
|
||||
const moduleExports = parcelRequire(metadata[ID]);
|
||||
return moduleExports[metadata[NAME]];
|
||||
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
|
||||
return moduleExports[metadata[NAME]];
|
||||
}
|
||||
return (undefined: any);
|
||||
}
|
||||
|
||||
export function getModuleDebugInfo<T>(
|
||||
|
||||
@@ -578,16 +578,23 @@ export function decodeReplyFromBusboy<T>(
|
||||
// we queue any fields we receive until the previous file is done.
|
||||
queuedFields.push(name, value);
|
||||
} else {
|
||||
resolveField(response, name, value);
|
||||
try {
|
||||
resolveField(response, name, value);
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
|
||||
if (encoding.toLowerCase() === 'base64') {
|
||||
throw new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
busboyStream.destroy(
|
||||
new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
pendingFiles++;
|
||||
const file = resolveFileInfo(response, name, filename, mimeType);
|
||||
@@ -595,14 +602,18 @@ export function decodeReplyFromBusboy<T>(
|
||||
resolveFileChunk(response, file, chunk);
|
||||
});
|
||||
value.on('end', () => {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
try {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-server-dom-turbopack",
|
||||
"description": "React Server Components bindings for DOM using Turbopack. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -79,8 +79,8 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react": "^19.2.2",
|
||||
"react-dom": "^19.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn-loose": "^8.3.0",
|
||||
|
||||
@@ -102,6 +102,12 @@ function bind(this: ServerReference<any>): any {
|
||||
return newFn;
|
||||
}
|
||||
|
||||
const serverReferenceToString = {
|
||||
value: () => 'function () { [omitted code] }',
|
||||
configurable: true,
|
||||
writable: true,
|
||||
};
|
||||
|
||||
export function registerServerReference<T: Function>(
|
||||
reference: T,
|
||||
id: string,
|
||||
@@ -125,12 +131,14 @@ export function registerServerReference<T: Function>(
|
||||
configurable: true,
|
||||
},
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
}
|
||||
: {
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
}) as PropertyDescriptorMap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
addChunkDebugInfo,
|
||||
} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import hasOwnProperty from 'shared/hasOwnProperty';
|
||||
|
||||
export type ServerConsumerModuleMap = null | {
|
||||
[clientId: string]: {
|
||||
[clientExportName: string]: ClientReferenceManifestEntry,
|
||||
@@ -245,7 +247,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
|
||||
// default property of this if it was an ESM interop module.
|
||||
return moduleExports.__esModule ? moduleExports.default : moduleExports;
|
||||
}
|
||||
return moduleExports[metadata[NAME]];
|
||||
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
|
||||
return moduleExports[metadata[NAME]];
|
||||
}
|
||||
return (undefined: any);
|
||||
}
|
||||
|
||||
export function getModuleDebugInfo<T>(
|
||||
|
||||
@@ -570,16 +570,23 @@ function decodeReplyFromBusboy<T>(
|
||||
// we queue any fields we receive until the previous file is done.
|
||||
queuedFields.push(name, value);
|
||||
} else {
|
||||
resolveField(response, name, value);
|
||||
try {
|
||||
resolveField(response, name, value);
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
|
||||
if (encoding.toLowerCase() === 'base64') {
|
||||
throw new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
busboyStream.destroy(
|
||||
new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
pendingFiles++;
|
||||
const file = resolveFileInfo(response, name, filename, mimeType);
|
||||
@@ -587,14 +594,18 @@ function decodeReplyFromBusboy<T>(
|
||||
resolveFileChunk(response, file, chunk);
|
||||
});
|
||||
value.on('end', () => {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
try {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
5
packages/react-server-dom-unbundled/README.md
Normal file
5
packages/react-server-dom-unbundled/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# react-server-dom-unbundled
|
||||
|
||||
Test-only React Flight bindings for DOM using Node.js.
|
||||
|
||||
This only exists for internal testing.
|
||||
@@ -7,4 +7,4 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export * from './src/client/react-flight-dom-client.node.unbundled';
|
||||
export * from './src/client/react-flight-dom-client.node';
|
||||
3
packages/react-server-dom-unbundled/esm/package.json
Normal file
3
packages/react-server-dom-unbundled/esm/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export * from '../src/ReactFlightUnbundledNodeLoader.js';
|
||||
10
packages/react-server-dom-unbundled/index.js
vendored
Normal file
10
packages/react-server-dom-unbundled/index.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
throw new Error('Use react-server-dom-webpack/client instead.');
|
||||
10
packages/react-server-dom-unbundled/node-register.js
vendored
Normal file
10
packages/react-server-dom-unbundled/node-register.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
module.exports = require('./src/ReactFlightUnbundledNodeRegister');
|
||||
7
packages/react-server-dom-unbundled/npm/client.js
vendored
Normal file
7
packages/react-server-dom-unbundled/npm/client.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-server-dom-unbundled-client.node.production.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-server-dom-unbundled-client.node.development.js');
|
||||
}
|
||||
3
packages/react-server-dom-unbundled/npm/esm/package.json
Normal file
3
packages/react-server-dom-unbundled/npm/esm/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
12
packages/react-server-dom-unbundled/npm/index.js
vendored
Normal file
12
packages/react-server-dom-unbundled/npm/index.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
throw new Error('Use react-server-dom-unbundled/client instead.');
|
||||
3
packages/react-server-dom-unbundled/npm/node-register.js
vendored
Normal file
3
packages/react-server-dom-unbundled/npm/node-register.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./cjs/react-server-dom-unbundled-node-register.js');
|
||||
6
packages/react-server-dom-unbundled/npm/server.js
vendored
Normal file
6
packages/react-server-dom-unbundled/npm/server.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
throw new Error(
|
||||
'The React Server Writer cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.'
|
||||
);
|
||||
20
packages/react-server-dom-unbundled/npm/server.node.js
Normal file
20
packages/react-server-dom-unbundled/npm/server.node.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-unbundled-server.node.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-unbundled-server.node.development.js');
|
||||
}
|
||||
|
||||
exports.renderToReadableStream = s.renderToReadableStream;
|
||||
exports.renderToPipeableStream = s.renderToPipeableStream;
|
||||
exports.decodeReply = s.decodeReply;
|
||||
exports.decodeReplyFromBusboy = s.decodeReplyFromBusboy;
|
||||
exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
|
||||
exports.decodeAction = s.decodeAction;
|
||||
exports.decodeFormState = s.decodeFormState;
|
||||
exports.registerServerReference = s.registerServerReference;
|
||||
exports.registerClientReference = s.registerClientReference;
|
||||
exports.createClientModuleProxy = s.createClientModuleProxy;
|
||||
exports.createTemporaryReferenceSet = s.createTemporaryReferenceSet;
|
||||
6
packages/react-server-dom-unbundled/npm/static.js
vendored
Normal file
6
packages/react-server-dom-unbundled/npm/static.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
throw new Error(
|
||||
'The React Server Writer cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.'
|
||||
);
|
||||
11
packages/react-server-dom-unbundled/npm/static.node.js
Normal file
11
packages/react-server-dom-unbundled/npm/static.node.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-unbundled-server.node.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-unbundled-server.node.development.js');
|
||||
}
|
||||
|
||||
exports.prerender = s.prerender;
|
||||
exports.prerenderToNodeStream = s.prerenderToNodeStream;
|
||||
46
packages/react-server-dom-unbundled/package.json
Normal file
46
packages/react-server-dom-unbundled/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "react-server-dom-unbundled",
|
||||
"description": "React Server Components bindings for DOM using Node.js. This only exists for internal testing.",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"client.js",
|
||||
"server.js",
|
||||
"server.node.js",
|
||||
"static.js",
|
||||
"static.node.js",
|
||||
"node-register.js",
|
||||
"cjs/",
|
||||
"esm/"
|
||||
],
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./client": "./client.js",
|
||||
"./server": {
|
||||
"react-server": "./server.node.js",
|
||||
"default": "./server.js"
|
||||
},
|
||||
"./server.node": "./server.node.js",
|
||||
"./static": {
|
||||
"react-server": "./static.node.js",
|
||||
"default": "./static.js"
|
||||
},
|
||||
"./static.node": "./static.node.js",
|
||||
"./node-loader": "./esm/react-server-dom-unbundled-node-loader.production.js",
|
||||
"./node-register": "./node-register.js",
|
||||
"./src/*": "./src/*.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "index.js",
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn-loose": "^8.3.0",
|
||||
"webpack-sources": "^3.2.0"
|
||||
}
|
||||
}
|
||||
13
packages/react-server-dom-unbundled/server.js
vendored
Normal file
13
packages/react-server-dom-unbundled/server.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
throw new Error(
|
||||
'The React Server cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.',
|
||||
);
|
||||
@@ -19,4 +19,4 @@ export {
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './src/server/react-flight-dom-server.node.unbundled';
|
||||
} from './src/server/react-flight-dom-server.node';
|
||||
804
packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js
vendored
Normal file
804
packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js
vendored
Normal file
@@ -0,0 +1,804 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as acorn from 'acorn-loose';
|
||||
|
||||
import readMappings from 'webpack-sources/lib/helpers/readMappings.js';
|
||||
import createMappingsSerializer from 'webpack-sources/lib/helpers/createMappingsSerializer.js';
|
||||
|
||||
type ResolveContext = {
|
||||
conditions: Array<string>,
|
||||
parentURL: string | void,
|
||||
};
|
||||
|
||||
type ResolveFunction = (
|
||||
string,
|
||||
ResolveContext,
|
||||
ResolveFunction,
|
||||
) => {url: string} | Promise<{url: string}>;
|
||||
|
||||
type GetSourceContext = {
|
||||
format: string,
|
||||
};
|
||||
|
||||
type GetSourceFunction = (
|
||||
string,
|
||||
GetSourceContext,
|
||||
GetSourceFunction,
|
||||
) => Promise<{source: Source}>;
|
||||
|
||||
type TransformSourceContext = {
|
||||
format: string,
|
||||
url: string,
|
||||
};
|
||||
|
||||
type TransformSourceFunction = (
|
||||
Source,
|
||||
TransformSourceContext,
|
||||
TransformSourceFunction,
|
||||
) => Promise<{source: Source}>;
|
||||
|
||||
type LoadContext = {
|
||||
conditions: Array<string>,
|
||||
format: string | null | void,
|
||||
importAssertions: Object,
|
||||
};
|
||||
|
||||
type LoadFunction = (
|
||||
string,
|
||||
LoadContext,
|
||||
LoadFunction,
|
||||
) => Promise<{format: string, shortCircuit?: boolean, source: Source}>;
|
||||
|
||||
type Source = string | ArrayBuffer | Uint8Array;
|
||||
|
||||
let warnedAboutConditionsFlag = false;
|
||||
|
||||
let stashedGetSource: null | GetSourceFunction = null;
|
||||
let stashedResolve: null | ResolveFunction = null;
|
||||
|
||||
export async function resolve(
|
||||
specifier: string,
|
||||
context: ResolveContext,
|
||||
defaultResolve: ResolveFunction,
|
||||
): Promise<{url: string}> {
|
||||
// We stash this in case we end up needing to resolve export * statements later.
|
||||
stashedResolve = defaultResolve;
|
||||
|
||||
if (!context.conditions.includes('react-server')) {
|
||||
context = {
|
||||
...context,
|
||||
conditions: [...context.conditions, 'react-server'],
|
||||
};
|
||||
if (!warnedAboutConditionsFlag) {
|
||||
warnedAboutConditionsFlag = true;
|
||||
// eslint-disable-next-line react-internal/no-production-logging
|
||||
console.warn(
|
||||
'You did not run Node.js with the `--conditions react-server` flag. ' +
|
||||
'Any "react-server" override will only work with ESM imports.',
|
||||
);
|
||||
}
|
||||
}
|
||||
return await defaultResolve(specifier, context, defaultResolve);
|
||||
}
|
||||
|
||||
export async function getSource(
|
||||
url: string,
|
||||
context: GetSourceContext,
|
||||
defaultGetSource: GetSourceFunction,
|
||||
): Promise<{source: Source}> {
|
||||
// We stash this in case we end up needing to resolve export * statements later.
|
||||
stashedGetSource = defaultGetSource;
|
||||
return defaultGetSource(url, context, defaultGetSource);
|
||||
}
|
||||
|
||||
type ExportedEntry = {
|
||||
localName: string,
|
||||
exportedName: string,
|
||||
type: null | string,
|
||||
loc: {
|
||||
start: {line: number, column: number},
|
||||
end: {line: number, column: number},
|
||||
},
|
||||
originalLine: number,
|
||||
originalColumn: number,
|
||||
originalSource: number,
|
||||
nameIndex: number,
|
||||
};
|
||||
|
||||
function addExportedEntry(
|
||||
exportedEntries: Array<ExportedEntry>,
|
||||
localNames: Set<string>,
|
||||
localName: string,
|
||||
exportedName: string,
|
||||
type: null | 'function',
|
||||
loc: {
|
||||
start: {line: number, column: number},
|
||||
end: {line: number, column: number},
|
||||
},
|
||||
) {
|
||||
if (localNames.has(localName)) {
|
||||
// If the same local name is exported more than once, we only need one of the names.
|
||||
return;
|
||||
}
|
||||
exportedEntries.push({
|
||||
localName,
|
||||
exportedName,
|
||||
type,
|
||||
loc,
|
||||
originalLine: -1,
|
||||
originalColumn: -1,
|
||||
originalSource: -1,
|
||||
nameIndex: -1,
|
||||
});
|
||||
}
|
||||
|
||||
function addLocalExportedNames(
|
||||
exportedEntries: Array<ExportedEntry>,
|
||||
localNames: Set<string>,
|
||||
node: any,
|
||||
) {
|
||||
switch (node.type) {
|
||||
case 'Identifier':
|
||||
addExportedEntry(
|
||||
exportedEntries,
|
||||
localNames,
|
||||
node.name,
|
||||
node.name,
|
||||
null,
|
||||
node.loc,
|
||||
);
|
||||
return;
|
||||
case 'ObjectPattern':
|
||||
for (let i = 0; i < node.properties.length; i++)
|
||||
addLocalExportedNames(exportedEntries, localNames, node.properties[i]);
|
||||
return;
|
||||
case 'ArrayPattern':
|
||||
for (let i = 0; i < node.elements.length; i++) {
|
||||
const element = node.elements[i];
|
||||
if (element)
|
||||
addLocalExportedNames(exportedEntries, localNames, element);
|
||||
}
|
||||
return;
|
||||
case 'Property':
|
||||
addLocalExportedNames(exportedEntries, localNames, node.value);
|
||||
return;
|
||||
case 'AssignmentPattern':
|
||||
addLocalExportedNames(exportedEntries, localNames, node.left);
|
||||
return;
|
||||
case 'RestElement':
|
||||
addLocalExportedNames(exportedEntries, localNames, node.argument);
|
||||
return;
|
||||
case 'ParenthesizedExpression':
|
||||
addLocalExportedNames(exportedEntries, localNames, node.expression);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function transformServerModule(
|
||||
source: string,
|
||||
program: any,
|
||||
url: string,
|
||||
sourceMap: any,
|
||||
loader: LoadFunction,
|
||||
): string {
|
||||
const body = program.body;
|
||||
|
||||
// This entry list needs to be in source location order.
|
||||
const exportedEntries: Array<ExportedEntry> = [];
|
||||
// Dedupe set.
|
||||
const localNames: Set<string> = new Set();
|
||||
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const node = body[i];
|
||||
switch (node.type) {
|
||||
case 'ExportAllDeclaration':
|
||||
// If export * is used, the other file needs to explicitly opt into "use server" too.
|
||||
break;
|
||||
case 'ExportDefaultDeclaration':
|
||||
if (node.declaration.type === 'Identifier') {
|
||||
addExportedEntry(
|
||||
exportedEntries,
|
||||
localNames,
|
||||
node.declaration.name,
|
||||
'default',
|
||||
null,
|
||||
node.declaration.loc,
|
||||
);
|
||||
} else if (node.declaration.type === 'FunctionDeclaration') {
|
||||
if (node.declaration.id) {
|
||||
addExportedEntry(
|
||||
exportedEntries,
|
||||
localNames,
|
||||
node.declaration.id.name,
|
||||
'default',
|
||||
'function',
|
||||
node.declaration.id.loc,
|
||||
);
|
||||
} else {
|
||||
// TODO: This needs to be rewritten inline because it doesn't have a local name.
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case 'ExportNamedDeclaration':
|
||||
if (node.declaration) {
|
||||
if (node.declaration.type === 'VariableDeclaration') {
|
||||
const declarations = node.declaration.declarations;
|
||||
for (let j = 0; j < declarations.length; j++) {
|
||||
addLocalExportedNames(
|
||||
exportedEntries,
|
||||
localNames,
|
||||
declarations[j].id,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const name = node.declaration.id.name;
|
||||
addExportedEntry(
|
||||
exportedEntries,
|
||||
localNames,
|
||||
name,
|
||||
name,
|
||||
|
||||
node.declaration.type === 'FunctionDeclaration'
|
||||
? 'function'
|
||||
: null,
|
||||
node.declaration.id.loc,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (node.specifiers) {
|
||||
const specifiers = node.specifiers;
|
||||
for (let j = 0; j < specifiers.length; j++) {
|
||||
const specifier = specifiers[j];
|
||||
addExportedEntry(
|
||||
exportedEntries,
|
||||
localNames,
|
||||
specifier.local.name,
|
||||
specifier.exported.name,
|
||||
null,
|
||||
specifier.local.loc,
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mappings =
|
||||
sourceMap && typeof sourceMap.mappings === 'string'
|
||||
? sourceMap.mappings
|
||||
: '';
|
||||
let newSrc = source;
|
||||
|
||||
if (exportedEntries.length > 0) {
|
||||
let lastSourceIndex = 0;
|
||||
let lastOriginalLine = 0;
|
||||
let lastOriginalColumn = 0;
|
||||
let lastNameIndex = 0;
|
||||
let sourceLineCount = 0;
|
||||
let lastMappedLine = 0;
|
||||
|
||||
if (sourceMap) {
|
||||
// We iterate source mapping entries and our matched exports in parallel to source map
|
||||
// them to their original location.
|
||||
let nextEntryIdx = 0;
|
||||
let nextEntryLine = exportedEntries[nextEntryIdx].loc.start.line;
|
||||
let nextEntryColumn = exportedEntries[nextEntryIdx].loc.start.column;
|
||||
readMappings(
|
||||
mappings,
|
||||
(
|
||||
generatedLine: number,
|
||||
generatedColumn: number,
|
||||
sourceIndex: number,
|
||||
originalLine: number,
|
||||
originalColumn: number,
|
||||
nameIndex: number,
|
||||
) => {
|
||||
if (
|
||||
generatedLine > nextEntryLine ||
|
||||
(generatedLine === nextEntryLine &&
|
||||
generatedColumn > nextEntryColumn)
|
||||
) {
|
||||
// We're past the entry which means that the best match we have is the previous entry.
|
||||
if (lastMappedLine === nextEntryLine) {
|
||||
// Match
|
||||
exportedEntries[nextEntryIdx].originalLine = lastOriginalLine;
|
||||
exportedEntries[nextEntryIdx].originalColumn = lastOriginalColumn;
|
||||
exportedEntries[nextEntryIdx].originalSource = lastSourceIndex;
|
||||
exportedEntries[nextEntryIdx].nameIndex = lastNameIndex;
|
||||
} else {
|
||||
// Skip if we didn't have any mappings on the exported line.
|
||||
}
|
||||
nextEntryIdx++;
|
||||
if (nextEntryIdx < exportedEntries.length) {
|
||||
nextEntryLine = exportedEntries[nextEntryIdx].loc.start.line;
|
||||
nextEntryColumn = exportedEntries[nextEntryIdx].loc.start.column;
|
||||
} else {
|
||||
nextEntryLine = -1;
|
||||
nextEntryColumn = -1;
|
||||
}
|
||||
}
|
||||
lastMappedLine = generatedLine;
|
||||
if (sourceIndex > -1) {
|
||||
lastSourceIndex = sourceIndex;
|
||||
}
|
||||
if (originalLine > -1) {
|
||||
lastOriginalLine = originalLine;
|
||||
}
|
||||
if (originalColumn > -1) {
|
||||
lastOriginalColumn = originalColumn;
|
||||
}
|
||||
if (nameIndex > -1) {
|
||||
lastNameIndex = nameIndex;
|
||||
}
|
||||
},
|
||||
);
|
||||
if (nextEntryIdx < exportedEntries.length) {
|
||||
if (lastMappedLine === nextEntryLine) {
|
||||
// Match
|
||||
exportedEntries[nextEntryIdx].originalLine = lastOriginalLine;
|
||||
exportedEntries[nextEntryIdx].originalColumn = lastOriginalColumn;
|
||||
exportedEntries[nextEntryIdx].originalSource = lastSourceIndex;
|
||||
exportedEntries[nextEntryIdx].nameIndex = lastNameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
for (
|
||||
let lastIdx = mappings.length - 1;
|
||||
lastIdx >= 0 && mappings[lastIdx] === ';';
|
||||
lastIdx--
|
||||
) {
|
||||
// If the last mapped lines don't contain any segments, we don't get a callback from readMappings
|
||||
// so we need to pad the number of mapped lines, with one for each empty line.
|
||||
lastMappedLine++;
|
||||
}
|
||||
|
||||
sourceLineCount = program.loc.end.line;
|
||||
if (sourceLineCount < lastMappedLine) {
|
||||
throw new Error(
|
||||
'The source map has more mappings than there are lines.',
|
||||
);
|
||||
}
|
||||
// If the original source string had more lines than there are mappings in the source map.
|
||||
// Add some extra padding of unmapped lines so that any lines that we add line up.
|
||||
for (
|
||||
let extraLines = sourceLineCount - lastMappedLine;
|
||||
extraLines > 0;
|
||||
extraLines--
|
||||
) {
|
||||
mappings += ';';
|
||||
}
|
||||
} else {
|
||||
// If a file doesn't have a source map then we generate a blank source map that just
|
||||
// contains the original content and segments pointing to the original lines.
|
||||
sourceLineCount = 1;
|
||||
let idx = -1;
|
||||
while ((idx = source.indexOf('\n', idx + 1)) !== -1) {
|
||||
sourceLineCount++;
|
||||
}
|
||||
mappings = 'AAAA' + ';AACA'.repeat(sourceLineCount - 1);
|
||||
sourceMap = {
|
||||
version: 3,
|
||||
sources: [url],
|
||||
sourcesContent: [source],
|
||||
mappings: mappings,
|
||||
sourceRoot: '',
|
||||
};
|
||||
lastSourceIndex = 0;
|
||||
lastOriginalLine = sourceLineCount;
|
||||
lastOriginalColumn = 0;
|
||||
lastNameIndex = -1;
|
||||
lastMappedLine = sourceLineCount;
|
||||
|
||||
for (let i = 0; i < exportedEntries.length; i++) {
|
||||
// Point each entry to original location.
|
||||
const entry = exportedEntries[i];
|
||||
entry.originalSource = 0;
|
||||
entry.originalLine = entry.loc.start.line;
|
||||
// We use column zero since we do the short-hand line-only source maps above.
|
||||
entry.originalColumn = 0; // entry.loc.start.column;
|
||||
}
|
||||
}
|
||||
|
||||
newSrc += '\n\n;';
|
||||
newSrc +=
|
||||
'import {registerServerReference} from "react-server-dom-webpack/server";\n';
|
||||
if (mappings) {
|
||||
mappings += ';;';
|
||||
}
|
||||
|
||||
const createMapping = createMappingsSerializer();
|
||||
|
||||
// Create an empty mapping pointing to where we last left off to reset the counters.
|
||||
let generatedLine = 1;
|
||||
createMapping(
|
||||
generatedLine,
|
||||
0,
|
||||
lastSourceIndex,
|
||||
lastOriginalLine,
|
||||
lastOriginalColumn,
|
||||
lastNameIndex,
|
||||
);
|
||||
for (let i = 0; i < exportedEntries.length; i++) {
|
||||
const entry = exportedEntries[i];
|
||||
generatedLine++;
|
||||
if (entry.type !== 'function') {
|
||||
// We first check if the export is a function and if so annotate it.
|
||||
newSrc += 'if (typeof ' + entry.localName + ' === "function") ';
|
||||
}
|
||||
newSrc += 'registerServerReference(' + entry.localName + ',';
|
||||
newSrc += JSON.stringify(url) + ',';
|
||||
newSrc += JSON.stringify(entry.exportedName) + ');\n';
|
||||
|
||||
mappings += createMapping(
|
||||
generatedLine,
|
||||
0,
|
||||
entry.originalSource,
|
||||
entry.originalLine,
|
||||
entry.originalColumn,
|
||||
entry.nameIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceMap) {
|
||||
// Override with an new mappings and serialize an inline source map.
|
||||
sourceMap.mappings = mappings;
|
||||
newSrc +=
|
||||
'//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
|
||||
Buffer.from(JSON.stringify(sourceMap)).toString('base64');
|
||||
}
|
||||
|
||||
return newSrc;
|
||||
}
|
||||
|
||||
function addExportNames(names: Array<string>, node: any) {
|
||||
switch (node.type) {
|
||||
case 'Identifier':
|
||||
names.push(node.name);
|
||||
return;
|
||||
case 'ObjectPattern':
|
||||
for (let i = 0; i < node.properties.length; i++)
|
||||
addExportNames(names, node.properties[i]);
|
||||
return;
|
||||
case 'ArrayPattern':
|
||||
for (let i = 0; i < node.elements.length; i++) {
|
||||
const element = node.elements[i];
|
||||
if (element) addExportNames(names, element);
|
||||
}
|
||||
return;
|
||||
case 'Property':
|
||||
addExportNames(names, node.value);
|
||||
return;
|
||||
case 'AssignmentPattern':
|
||||
addExportNames(names, node.left);
|
||||
return;
|
||||
case 'RestElement':
|
||||
addExportNames(names, node.argument);
|
||||
return;
|
||||
case 'ParenthesizedExpression':
|
||||
addExportNames(names, node.expression);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveClientImport(
|
||||
specifier: string,
|
||||
parentURL: string,
|
||||
): {url: string} | Promise<{url: string}> {
|
||||
// Resolve an import specifier as if it was loaded by the client. This doesn't use
|
||||
// the overrides that this loader does but instead reverts to the default.
|
||||
// This resolution algorithm will not necessarily have the same configuration
|
||||
// as the actual client loader. It should mostly work and if it doesn't you can
|
||||
// always convert to explicit exported names instead.
|
||||
const conditions = ['node', 'import'];
|
||||
if (stashedResolve === null) {
|
||||
throw new Error(
|
||||
'Expected resolve to have been called before transformSource',
|
||||
);
|
||||
}
|
||||
return stashedResolve(specifier, {conditions, parentURL}, stashedResolve);
|
||||
}
|
||||
|
||||
async function parseExportNamesInto(
|
||||
body: any,
|
||||
names: Array<string>,
|
||||
parentURL: string,
|
||||
loader: LoadFunction,
|
||||
): Promise<void> {
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const node = body[i];
|
||||
switch (node.type) {
|
||||
case 'ExportAllDeclaration':
|
||||
if (node.exported) {
|
||||
addExportNames(names, node.exported);
|
||||
continue;
|
||||
} else {
|
||||
const {url} = await resolveClientImport(node.source.value, parentURL);
|
||||
const {source} = await loader(
|
||||
url,
|
||||
{format: 'module', conditions: [], importAssertions: {}},
|
||||
loader,
|
||||
);
|
||||
if (typeof source !== 'string') {
|
||||
throw new Error('Expected the transformed source to be a string.');
|
||||
}
|
||||
let childBody;
|
||||
try {
|
||||
childBody = acorn.parse(source, {
|
||||
ecmaVersion: '2024',
|
||||
sourceType: 'module',
|
||||
}).body;
|
||||
} catch (x) {
|
||||
// eslint-disable-next-line react-internal/no-production-logging
|
||||
console.error('Error parsing %s %s', url, x.message);
|
||||
continue;
|
||||
}
|
||||
await parseExportNamesInto(childBody, names, url, loader);
|
||||
continue;
|
||||
}
|
||||
case 'ExportDefaultDeclaration':
|
||||
names.push('default');
|
||||
continue;
|
||||
case 'ExportNamedDeclaration':
|
||||
if (node.declaration) {
|
||||
if (node.declaration.type === 'VariableDeclaration') {
|
||||
const declarations = node.declaration.declarations;
|
||||
for (let j = 0; j < declarations.length; j++) {
|
||||
addExportNames(names, declarations[j].id);
|
||||
}
|
||||
} else {
|
||||
addExportNames(names, node.declaration.id);
|
||||
}
|
||||
}
|
||||
if (node.specifiers) {
|
||||
const specifiers = node.specifiers;
|
||||
for (let j = 0; j < specifiers.length; j++) {
|
||||
addExportNames(names, specifiers[j].exported);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function transformClientModule(
|
||||
program: any,
|
||||
url: string,
|
||||
sourceMap: any,
|
||||
loader: LoadFunction,
|
||||
): Promise<string> {
|
||||
const body = program.body;
|
||||
|
||||
const names: Array<string> = [];
|
||||
|
||||
await parseExportNamesInto(body, names, url, loader);
|
||||
|
||||
if (names.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let newSrc =
|
||||
'import {registerClientReference} from "react-server-dom-webpack/server";\n';
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
if (name === 'default') {
|
||||
newSrc += 'export default ';
|
||||
newSrc += 'registerClientReference(function() {';
|
||||
newSrc +=
|
||||
'throw new Error(' +
|
||||
JSON.stringify(
|
||||
`Attempted to call the default export of ${url} from the server ` +
|
||||
`but it's on the client. It's not possible to invoke a client function from ` +
|
||||
`the server, it can only be rendered as a Component or passed to props of a ` +
|
||||
`Client Component.`,
|
||||
) +
|
||||
');';
|
||||
} else {
|
||||
newSrc += 'export const ' + name + ' = ';
|
||||
newSrc += 'registerClientReference(function() {';
|
||||
newSrc +=
|
||||
'throw new Error(' +
|
||||
JSON.stringify(
|
||||
`Attempted to call ${name}() from the server but ${name} is on the client. ` +
|
||||
`It's not possible to invoke a client function from the server, it can ` +
|
||||
`only be rendered as a Component or passed to props of a Client Component.`,
|
||||
) +
|
||||
');';
|
||||
}
|
||||
newSrc += '},';
|
||||
newSrc += JSON.stringify(url) + ',';
|
||||
newSrc += JSON.stringify(name) + ');\n';
|
||||
}
|
||||
|
||||
// TODO: Generate source maps for Client Reference functions so they can point to their
|
||||
// original locations.
|
||||
return newSrc;
|
||||
}
|
||||
|
||||
async function loadClientImport(
|
||||
url: string,
|
||||
defaultTransformSource: TransformSourceFunction,
|
||||
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
|
||||
if (stashedGetSource === null) {
|
||||
throw new Error(
|
||||
'Expected getSource to have been called before transformSource',
|
||||
);
|
||||
}
|
||||
// TODO: Validate that this is another module by calling getFormat.
|
||||
const {source} = await stashedGetSource(
|
||||
url,
|
||||
{format: 'module'},
|
||||
stashedGetSource,
|
||||
);
|
||||
const result = await defaultTransformSource(
|
||||
source,
|
||||
{format: 'module', url},
|
||||
defaultTransformSource,
|
||||
);
|
||||
return {format: 'module', source: result.source};
|
||||
}
|
||||
|
||||
async function transformModuleIfNeeded(
|
||||
source: string,
|
||||
url: string,
|
||||
loader: LoadFunction,
|
||||
): Promise<string> {
|
||||
// Do a quick check for the exact string. If it doesn't exist, don't
|
||||
// bother parsing.
|
||||
if (
|
||||
source.indexOf('use client') === -1 &&
|
||||
source.indexOf('use server') === -1
|
||||
) {
|
||||
return source;
|
||||
}
|
||||
|
||||
let sourceMappingURL = null;
|
||||
let sourceMappingStart = 0;
|
||||
let sourceMappingEnd = 0;
|
||||
let sourceMappingLines = 0;
|
||||
|
||||
let program;
|
||||
try {
|
||||
program = acorn.parse(source, {
|
||||
ecmaVersion: '2024',
|
||||
sourceType: 'module',
|
||||
locations: true,
|
||||
onComment(
|
||||
block: boolean,
|
||||
text: string,
|
||||
start: number,
|
||||
end: number,
|
||||
startLoc: {line: number, column: number},
|
||||
endLoc: {line: number, column: number},
|
||||
) {
|
||||
if (
|
||||
text.startsWith('# sourceMappingURL=') ||
|
||||
text.startsWith('@ sourceMappingURL=')
|
||||
) {
|
||||
sourceMappingURL = text.slice(19);
|
||||
sourceMappingStart = start;
|
||||
sourceMappingEnd = end;
|
||||
sourceMappingLines = endLoc.line - startLoc.line;
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (x) {
|
||||
// eslint-disable-next-line react-internal/no-production-logging
|
||||
console.error('Error parsing %s %s', url, x.message);
|
||||
return source;
|
||||
}
|
||||
|
||||
let useClient = false;
|
||||
let useServer = false;
|
||||
|
||||
const body = program.body;
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const node = body[i];
|
||||
if (node.type !== 'ExpressionStatement' || !node.directive) {
|
||||
break;
|
||||
}
|
||||
if (node.directive === 'use client') {
|
||||
useClient = true;
|
||||
}
|
||||
if (node.directive === 'use server') {
|
||||
useServer = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useClient && !useServer) {
|
||||
return source;
|
||||
}
|
||||
|
||||
if (useClient && useServer) {
|
||||
throw new Error(
|
||||
'Cannot have both "use client" and "use server" directives in the same file.',
|
||||
);
|
||||
}
|
||||
|
||||
let sourceMap = null;
|
||||
if (sourceMappingURL) {
|
||||
const sourceMapResult = await loader(
|
||||
sourceMappingURL,
|
||||
// $FlowFixMe
|
||||
{
|
||||
format: 'json',
|
||||
conditions: [],
|
||||
importAssertions: {type: 'json'},
|
||||
importAttributes: {type: 'json'},
|
||||
},
|
||||
loader,
|
||||
);
|
||||
const sourceMapString =
|
||||
typeof sourceMapResult.source === 'string'
|
||||
? sourceMapResult.source
|
||||
: // $FlowFixMe
|
||||
sourceMapResult.source.toString('utf8');
|
||||
sourceMap = JSON.parse(sourceMapString);
|
||||
|
||||
// Strip the source mapping comment. We'll re-add it below if needed.
|
||||
source =
|
||||
source.slice(0, sourceMappingStart) +
|
||||
'\n'.repeat(sourceMappingLines) +
|
||||
source.slice(sourceMappingEnd);
|
||||
}
|
||||
|
||||
if (useClient) {
|
||||
return transformClientModule(program, url, sourceMap, loader);
|
||||
}
|
||||
|
||||
return transformServerModule(source, program, url, sourceMap, loader);
|
||||
}
|
||||
|
||||
export async function transformSource(
|
||||
source: Source,
|
||||
context: TransformSourceContext,
|
||||
defaultTransformSource: TransformSourceFunction,
|
||||
): Promise<{source: Source}> {
|
||||
const transformed = await defaultTransformSource(
|
||||
source,
|
||||
context,
|
||||
defaultTransformSource,
|
||||
);
|
||||
if (context.format === 'module') {
|
||||
const transformedSource = transformed.source;
|
||||
if (typeof transformedSource !== 'string') {
|
||||
throw new Error('Expected source to have been transformed to a string.');
|
||||
}
|
||||
const newSrc = await transformModuleIfNeeded(
|
||||
transformedSource,
|
||||
context.url,
|
||||
(url: string, ctx: LoadContext, defaultLoad: LoadFunction) => {
|
||||
return loadClientImport(url, defaultTransformSource);
|
||||
},
|
||||
);
|
||||
return {source: newSrc};
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
export async function load(
|
||||
url: string,
|
||||
context: LoadContext,
|
||||
defaultLoad: LoadFunction,
|
||||
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
|
||||
const result = await defaultLoad(url, context, defaultLoad);
|
||||
if (result.format === 'module') {
|
||||
if (typeof result.source !== 'string') {
|
||||
throw new Error('Expected source to have been loaded into a string.');
|
||||
}
|
||||
const newSrc = await transformModuleIfNeeded(
|
||||
result.source,
|
||||
url,
|
||||
defaultLoad,
|
||||
);
|
||||
return {format: 'module', source: newSrc};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
109
packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js
vendored
Normal file
109
packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const acorn = require('acorn-loose');
|
||||
|
||||
const url = require('url');
|
||||
|
||||
const Module = require('module');
|
||||
|
||||
module.exports = function register() {
|
||||
const Server: any = require('react-server-dom-unbundled/server');
|
||||
const registerServerReference = Server.registerServerReference;
|
||||
const createClientModuleProxy = Server.createClientModuleProxy;
|
||||
|
||||
// $FlowFixMe[prop-missing] found when upgrading Flow
|
||||
const originalCompile = Module.prototype._compile;
|
||||
|
||||
// $FlowFixMe[prop-missing] found when upgrading Flow
|
||||
Module.prototype._compile = function (
|
||||
this: any,
|
||||
content: string,
|
||||
filename: string,
|
||||
): void {
|
||||
// Do a quick check for the exact string. If it doesn't exist, don't
|
||||
// bother parsing.
|
||||
if (
|
||||
content.indexOf('use client') === -1 &&
|
||||
content.indexOf('use server') === -1
|
||||
) {
|
||||
return originalCompile.apply(this, arguments);
|
||||
}
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = acorn.parse(content, {
|
||||
ecmaVersion: '2024',
|
||||
sourceType: 'source',
|
||||
}).body;
|
||||
} catch (x) {
|
||||
console['error']('Error parsing %s %s', url, x.message);
|
||||
return originalCompile.apply(this, arguments);
|
||||
}
|
||||
|
||||
let useClient = false;
|
||||
let useServer = false;
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const node = body[i];
|
||||
if (node.type !== 'ExpressionStatement' || !node.directive) {
|
||||
break;
|
||||
}
|
||||
if (node.directive === 'use client') {
|
||||
useClient = true;
|
||||
}
|
||||
if (node.directive === 'use server') {
|
||||
useServer = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useClient && !useServer) {
|
||||
return originalCompile.apply(this, arguments);
|
||||
}
|
||||
|
||||
if (useClient && useServer) {
|
||||
throw new Error(
|
||||
'Cannot have both "use client" and "use server" directives in the same file.',
|
||||
);
|
||||
}
|
||||
|
||||
if (useClient) {
|
||||
const moduleId: string = (url.pathToFileURL(filename).href: any);
|
||||
this.exports = createClientModuleProxy(moduleId);
|
||||
}
|
||||
|
||||
if (useServer) {
|
||||
originalCompile.apply(this, arguments);
|
||||
|
||||
const moduleId: string = (url.pathToFileURL(filename).href: any);
|
||||
|
||||
const exports = this.exports;
|
||||
|
||||
// This module is imported server to server, but opts in to exposing functions by
|
||||
// reference. If there are any functions in the export.
|
||||
if (typeof exports === 'function') {
|
||||
// The module exports a function directly,
|
||||
registerServerReference(
|
||||
(exports: any),
|
||||
moduleId,
|
||||
// Represents the whole Module object instead of a particular import.
|
||||
null,
|
||||
);
|
||||
} else {
|
||||
const keys = Object.keys(exports);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const value = exports[keys[i]];
|
||||
if (typeof value === 'function') {
|
||||
registerServerReference((value: any), moduleId, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
360
packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js
vendored
Normal file
360
packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
|
||||
|
||||
export type ServerReference<T: Function> = T & {
|
||||
$$typeof: symbol,
|
||||
$$id: string,
|
||||
$$bound: null | Array<ReactClientValue>,
|
||||
$$location?: Error,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export type ClientReference<T> = {
|
||||
$$typeof: symbol,
|
||||
$$id: string,
|
||||
$$async: boolean,
|
||||
};
|
||||
|
||||
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
|
||||
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
|
||||
|
||||
export function isClientReference(reference: Object): boolean {
|
||||
return reference.$$typeof === CLIENT_REFERENCE_TAG;
|
||||
}
|
||||
|
||||
export function isServerReference(reference: Object): boolean {
|
||||
return reference.$$typeof === SERVER_REFERENCE_TAG;
|
||||
}
|
||||
|
||||
export function registerClientReference<T>(
|
||||
proxyImplementation: any,
|
||||
id: string,
|
||||
exportName: string,
|
||||
): ClientReference<T> {
|
||||
return registerClientReferenceImpl(
|
||||
proxyImplementation,
|
||||
id + '#' + exportName,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
function registerClientReferenceImpl<T>(
|
||||
proxyImplementation: any,
|
||||
id: string,
|
||||
async: boolean,
|
||||
): ClientReference<T> {
|
||||
return Object.defineProperties(proxyImplementation, {
|
||||
$$typeof: {value: CLIENT_REFERENCE_TAG},
|
||||
$$id: {value: id},
|
||||
$$async: {value: async},
|
||||
});
|
||||
}
|
||||
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const FunctionBind = Function.prototype.bind;
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const ArraySlice = Array.prototype.slice;
|
||||
function bind(this: ServerReference<any>): any {
|
||||
// $FlowFixMe[incompatible-call]
|
||||
const newFn = FunctionBind.apply(this, arguments);
|
||||
if (this.$$typeof === SERVER_REFERENCE_TAG) {
|
||||
if (__DEV__) {
|
||||
const thisBind = arguments[0];
|
||||
if (thisBind != null) {
|
||||
console.error(
|
||||
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
|
||||
);
|
||||
}
|
||||
}
|
||||
const args = ArraySlice.call(arguments, 1);
|
||||
const $$typeof = {value: SERVER_REFERENCE_TAG};
|
||||
const $$id = {value: this.$$id};
|
||||
const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args};
|
||||
return Object.defineProperties(
|
||||
(newFn: any),
|
||||
(__DEV__
|
||||
? {
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
$$location: {
|
||||
value: this.$$location,
|
||||
configurable: true,
|
||||
},
|
||||
bind: {value: bind, configurable: true},
|
||||
}
|
||||
: {
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
bind: {value: bind, configurable: true},
|
||||
}) as PropertyDescriptorMap,
|
||||
);
|
||||
}
|
||||
return newFn;
|
||||
}
|
||||
|
||||
const serverReferenceToString = {
|
||||
value: () => 'function () { [omitted code] }',
|
||||
configurable: true,
|
||||
writable: true,
|
||||
};
|
||||
|
||||
export function registerServerReference<T: Function>(
|
||||
reference: T,
|
||||
id: string,
|
||||
exportName: null | string,
|
||||
): ServerReference<T> {
|
||||
const $$typeof = {value: SERVER_REFERENCE_TAG};
|
||||
const $$id = {
|
||||
value: exportName === null ? id : id + '#' + exportName,
|
||||
configurable: true,
|
||||
};
|
||||
const $$bound = {value: null, configurable: true};
|
||||
return Object.defineProperties(
|
||||
(reference: any),
|
||||
__DEV__
|
||||
? ({
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
$$location: {
|
||||
value: Error('react-stack-top-frame'),
|
||||
configurable: true,
|
||||
},
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
} as PropertyDescriptorMap)
|
||||
: ({
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
} as PropertyDescriptorMap),
|
||||
);
|
||||
}
|
||||
|
||||
const PROMISE_PROTOTYPE = Promise.prototype;
|
||||
|
||||
const deepProxyHandlers: Proxy$traps<mixed> = {
|
||||
get: function (
|
||||
target: Function,
|
||||
name: string | symbol,
|
||||
receiver: Proxy<Function>,
|
||||
) {
|
||||
switch (name) {
|
||||
// These names are read by the Flight runtime if you end up using the exports object.
|
||||
case '$$typeof':
|
||||
// These names are a little too common. We should probably have a way to
|
||||
// have the Flight runtime extract the inner target instead.
|
||||
return target.$$typeof;
|
||||
case '$$id':
|
||||
return target.$$id;
|
||||
case '$$async':
|
||||
return target.$$async;
|
||||
case 'name':
|
||||
return target.name;
|
||||
case 'displayName':
|
||||
return undefined;
|
||||
// We need to special case this because createElement reads it if we pass this
|
||||
// reference.
|
||||
case 'defaultProps':
|
||||
return undefined;
|
||||
// React looks for debugInfo on thenables.
|
||||
case '_debugInfo':
|
||||
return undefined;
|
||||
// Avoid this attempting to be serialized.
|
||||
case 'toJSON':
|
||||
return undefined;
|
||||
case Symbol.toPrimitive:
|
||||
// $FlowFixMe[prop-missing]
|
||||
return Object.prototype[Symbol.toPrimitive];
|
||||
case Symbol.toStringTag:
|
||||
// $FlowFixMe[prop-missing]
|
||||
return Object.prototype[Symbol.toStringTag];
|
||||
case 'Provider':
|
||||
throw new Error(
|
||||
`Cannot render a Client Context Provider on the Server. ` +
|
||||
`Instead, you can export a Client Component wrapper ` +
|
||||
`that itself renders a Client Context Provider.`,
|
||||
);
|
||||
case 'then':
|
||||
throw new Error(
|
||||
`Cannot await or return from a thenable. ` +
|
||||
`You cannot await a client module from a server component.`,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
const expression = String(target.name) + '.' + String(name);
|
||||
throw new Error(
|
||||
`Cannot access ${expression} on the server. ` +
|
||||
'You cannot dot into a client module from a server component. ' +
|
||||
'You can only pass the imported name through.',
|
||||
);
|
||||
},
|
||||
set: function () {
|
||||
throw new Error('Cannot assign to a client module from a server module.');
|
||||
},
|
||||
};
|
||||
|
||||
function getReference(target: Function, name: string | symbol): $FlowFixMe {
|
||||
switch (name) {
|
||||
// These names are read by the Flight runtime if you end up using the exports object.
|
||||
case '$$typeof':
|
||||
return target.$$typeof;
|
||||
case '$$id':
|
||||
return target.$$id;
|
||||
case '$$async':
|
||||
return target.$$async;
|
||||
case 'name':
|
||||
return target.name;
|
||||
// We need to special case this because createElement reads it if we pass this
|
||||
// reference.
|
||||
case 'defaultProps':
|
||||
return undefined;
|
||||
// React looks for debugInfo on thenables.
|
||||
case '_debugInfo':
|
||||
return undefined;
|
||||
// Avoid this attempting to be serialized.
|
||||
case 'toJSON':
|
||||
return undefined;
|
||||
case Symbol.toPrimitive:
|
||||
// $FlowFixMe[prop-missing]
|
||||
return Object.prototype[Symbol.toPrimitive];
|
||||
case Symbol.toStringTag:
|
||||
// $FlowFixMe[prop-missing]
|
||||
return Object.prototype[Symbol.toStringTag];
|
||||
case '__esModule':
|
||||
// Something is conditionally checking which export to use. We'll pretend to be
|
||||
// an ESM compat module but then we'll check again on the client.
|
||||
const moduleId = target.$$id;
|
||||
target.default = registerClientReferenceImpl(
|
||||
(function () {
|
||||
throw new Error(
|
||||
`Attempted to call the default export of ${moduleId} from the server ` +
|
||||
`but it's on the client. It's not possible to invoke a client function from ` +
|
||||
`the server, it can only be rendered as a Component or passed to props of a ` +
|
||||
`Client Component.`,
|
||||
);
|
||||
}: any),
|
||||
target.$$id + '#',
|
||||
target.$$async,
|
||||
);
|
||||
return true;
|
||||
case 'then':
|
||||
if (target.then) {
|
||||
// Use a cached value
|
||||
return target.then;
|
||||
}
|
||||
if (!target.$$async) {
|
||||
// If this module is expected to return a Promise (such as an AsyncModule) then
|
||||
// we should resolve that with a client reference that unwraps the Promise on
|
||||
// the client.
|
||||
|
||||
const clientReference: ClientReference<any> =
|
||||
registerClientReferenceImpl(({}: any), target.$$id, true);
|
||||
const proxy = new Proxy(clientReference, proxyHandlers);
|
||||
|
||||
// Treat this as a resolved Promise for React's use()
|
||||
target.status = 'fulfilled';
|
||||
target.value = proxy;
|
||||
|
||||
const then = (target.then = registerClientReferenceImpl(
|
||||
(function then(resolve, reject: any) {
|
||||
// Expose to React.
|
||||
return Promise.resolve(resolve(proxy));
|
||||
}: any),
|
||||
// If this is not used as a Promise but is treated as a reference to a `.then`
|
||||
// export then we should treat it as a reference to that name.
|
||||
target.$$id + '#then',
|
||||
false,
|
||||
));
|
||||
return then;
|
||||
} else {
|
||||
// Since typeof .then === 'function' is a feature test we'd continue recursing
|
||||
// indefinitely if we return a function. Instead, we return an object reference
|
||||
// if we check further.
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (typeof name === 'symbol') {
|
||||
throw new Error(
|
||||
'Cannot read Symbol exports. Only named exports are supported on a client module ' +
|
||||
'imported on the server.',
|
||||
);
|
||||
}
|
||||
let cachedReference = target[name];
|
||||
if (!cachedReference) {
|
||||
const reference: ClientReference<any> = registerClientReferenceImpl(
|
||||
(function () {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
`Attempted to call ${String(name)}() from the server but ${String(
|
||||
name,
|
||||
)} is on the client. ` +
|
||||
`It's not possible to invoke a client function from the server, it can ` +
|
||||
`only be rendered as a Component or passed to props of a Client Component.`,
|
||||
);
|
||||
}: any),
|
||||
target.$$id + '#' + name,
|
||||
target.$$async,
|
||||
);
|
||||
Object.defineProperty((reference: any), 'name', {value: name});
|
||||
cachedReference = target[name] = new Proxy(reference, deepProxyHandlers);
|
||||
}
|
||||
return cachedReference;
|
||||
}
|
||||
|
||||
const proxyHandlers = {
|
||||
get: function (
|
||||
target: Function,
|
||||
name: string | symbol,
|
||||
receiver: Proxy<Function>,
|
||||
): $FlowFixMe {
|
||||
return getReference(target, name);
|
||||
},
|
||||
getOwnPropertyDescriptor: function (
|
||||
target: Function,
|
||||
name: string | symbol,
|
||||
): $FlowFixMe {
|
||||
let descriptor = Object.getOwnPropertyDescriptor(target, name);
|
||||
if (!descriptor) {
|
||||
descriptor = {
|
||||
value: getReference(target, name),
|
||||
writable: false,
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
};
|
||||
Object.defineProperty(target, name, descriptor);
|
||||
}
|
||||
return descriptor;
|
||||
},
|
||||
getPrototypeOf(target: Function): Object {
|
||||
// Pretend to be a Promise in case anyone asks.
|
||||
return PROMISE_PROTOTYPE;
|
||||
},
|
||||
set: function (): empty {
|
||||
throw new Error('Cannot assign to a client module from a server module.');
|
||||
},
|
||||
};
|
||||
|
||||
export function createClientModuleProxy<T>(
|
||||
moduleId: string,
|
||||
): ClientReference<T> {
|
||||
const clientReference: ClientReference<T> = registerClientReferenceImpl(
|
||||
({}: any),
|
||||
// Represents the whole Module object instead of a particular import.
|
||||
moduleId,
|
||||
false,
|
||||
);
|
||||
return new Proxy(clientReference, proxyHandlers);
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
} from '../shared/ReactFlightImportMetadata';
|
||||
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import hasOwnProperty from 'shared/hasOwnProperty';
|
||||
|
||||
export type ServerConsumerModuleMap = {
|
||||
[clientId: string]: {
|
||||
[clientExportName: string]: ClientReference<any>,
|
||||
@@ -158,7 +160,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
|
||||
// default property of this if it was an ESM interop module.
|
||||
return moduleExports.default;
|
||||
}
|
||||
return moduleExports[metadata.name];
|
||||
if (hasOwnProperty.call(moduleExports, metadata.name)) {
|
||||
return moduleExports[metadata.name];
|
||||
}
|
||||
return (undefined: any);
|
||||
}
|
||||
|
||||
export function getModuleDebugInfo<T>(metadata: ClientReference<T>): null {
|
||||
32
packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer.js
vendored
Normal file
32
packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {preinitScriptForSSR} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
export type ModuleLoading = null | {
|
||||
prefix: string,
|
||||
crossOrigin?: 'use-credentials' | '',
|
||||
};
|
||||
|
||||
export function prepareDestinationWithChunks(
|
||||
moduleLoading: ModuleLoading,
|
||||
// Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
|
||||
chunks: Array<string>,
|
||||
nonce: ?string,
|
||||
) {
|
||||
if (moduleLoading !== null) {
|
||||
for (let i = 1; i < chunks.length; i += 2) {
|
||||
preinitScriptForSSR(
|
||||
moduleLoading.prefix + chunks[i],
|
||||
nonce,
|
||||
moduleLoading.crossOrigin,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js
vendored
Normal file
249
packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
DebugChannel,
|
||||
FindSourceMapURLCallback,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
|
||||
import type {
|
||||
ServerConsumerModuleMap,
|
||||
ModuleLoading,
|
||||
ServerManifest,
|
||||
} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
type ServerConsumerManifest = {
|
||||
moduleMap: ServerConsumerModuleMap,
|
||||
moduleLoading: ModuleLoading,
|
||||
serverModuleMap: null | ServerManifest,
|
||||
};
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
createStreamState,
|
||||
getRoot,
|
||||
reportGlobalError,
|
||||
processBinaryChunk,
|
||||
close,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import {
|
||||
processReply,
|
||||
createServerReference as createServerReferenceImpl,
|
||||
} from 'react-client/src/ReactFlightReplyClient';
|
||||
|
||||
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
|
||||
|
||||
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
|
||||
|
||||
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
|
||||
|
||||
export type {TemporaryReferenceSet};
|
||||
|
||||
function noServerCall() {
|
||||
throw new Error(
|
||||
'Server Functions cannot be called during initial render. ' +
|
||||
'This would create a fetch waterfall. Try to use a Server Component ' +
|
||||
'to pass data to Client Components instead.',
|
||||
);
|
||||
}
|
||||
|
||||
export function createServerReference<A: Iterable<any>, T>(
|
||||
id: any,
|
||||
callServer: any,
|
||||
): (...A) => Promise<T> {
|
||||
return createServerReferenceImpl(id, noServerCall);
|
||||
}
|
||||
|
||||
type EncodeFormActionCallback = <A>(
|
||||
id: any,
|
||||
args: Promise<A>,
|
||||
) => ReactCustomFormAction;
|
||||
|
||||
export type Options = {
|
||||
serverConsumerManifest: ServerConsumerManifest,
|
||||
nonce?: string,
|
||||
encodeFormAction?: EncodeFormActionCallback,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
findSourceMapURL?: FindSourceMapURLCallback,
|
||||
replayConsoleLogs?: boolean,
|
||||
environmentName?: string,
|
||||
// For the Edge client we only support a single-direction debug channel.
|
||||
debugChannel?: {readable?: ReadableStream, ...},
|
||||
};
|
||||
|
||||
function createResponseFromOptions(options: Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
options.serverConsumerManifest.moduleMap,
|
||||
options.serverConsumerManifest.serverModuleMap,
|
||||
options.serverConsumerManifest.moduleLoading,
|
||||
noServerCall,
|
||||
options.encodeFormAction,
|
||||
typeof options.nonce === 'string' ? options.nonce : undefined,
|
||||
options && options.temporaryReferences
|
||||
? options.temporaryReferences
|
||||
: undefined,
|
||||
__DEV__ && options && options.findSourceMapURL
|
||||
? options.findSourceMapURL
|
||||
: undefined,
|
||||
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
function startReadingFromStream(
|
||||
response: FlightResponse,
|
||||
stream: ReadableStream,
|
||||
onDone: () => void,
|
||||
debugValue: mixed,
|
||||
): void {
|
||||
const streamState = createStreamState(response, debugValue);
|
||||
const reader = stream.getReader();
|
||||
function progress({
|
||||
done,
|
||||
value,
|
||||
}: {
|
||||
done: boolean,
|
||||
value: ?any,
|
||||
...
|
||||
}): void | Promise<void> {
|
||||
if (done) {
|
||||
return onDone();
|
||||
}
|
||||
const buffer: Uint8Array = (value: any);
|
||||
processBinaryChunk(response, streamState, buffer);
|
||||
return reader.read().then(progress).catch(error);
|
||||
}
|
||||
function error(e: any) {
|
||||
reportGlobalError(response, e);
|
||||
}
|
||||
reader.read().then(progress).catch(error);
|
||||
}
|
||||
|
||||
function createFromReadableStream<T>(
|
||||
stream: ReadableStream,
|
||||
options: Options,
|
||||
): Thenable<T> {
|
||||
const response: FlightResponse = createResponseFromOptions(options);
|
||||
|
||||
if (
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel &&
|
||||
options.debugChannel.readable
|
||||
) {
|
||||
let streamDoneCount = 0;
|
||||
const handleDone = () => {
|
||||
if (++streamDoneCount === 2) {
|
||||
close(response);
|
||||
}
|
||||
};
|
||||
startReadingFromStream(response, options.debugChannel.readable, handleDone);
|
||||
startReadingFromStream(response, stream, handleDone, stream);
|
||||
} else {
|
||||
startReadingFromStream(
|
||||
response,
|
||||
stream,
|
||||
close.bind(null, response),
|
||||
stream,
|
||||
);
|
||||
}
|
||||
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
function createFromFetch<T>(
|
||||
promiseForResponse: Promise<Response>,
|
||||
options: Options,
|
||||
): Thenable<T> {
|
||||
const response: FlightResponse = createResponseFromOptions(options);
|
||||
promiseForResponse.then(
|
||||
function (r) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel &&
|
||||
options.debugChannel.readable
|
||||
) {
|
||||
let streamDoneCount = 0;
|
||||
const handleDone = () => {
|
||||
if (++streamDoneCount === 2) {
|
||||
close(response);
|
||||
}
|
||||
};
|
||||
startReadingFromStream(
|
||||
response,
|
||||
options.debugChannel.readable,
|
||||
handleDone,
|
||||
);
|
||||
startReadingFromStream(response, (r.body: any), handleDone, r);
|
||||
} else {
|
||||
startReadingFromStream(
|
||||
response,
|
||||
(r.body: any),
|
||||
close.bind(null, response),
|
||||
r,
|
||||
);
|
||||
}
|
||||
},
|
||||
function (e) {
|
||||
reportGlobalError(response, e);
|
||||
},
|
||||
);
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
function encodeReply(
|
||||
value: ReactServerValue,
|
||||
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
|
||||
): Promise<
|
||||
string | URLSearchParams | FormData,
|
||||
> /* We don't use URLSearchParams yet but maybe */ {
|
||||
return new Promise((resolve, reject) => {
|
||||
const abort = processReply(
|
||||
value,
|
||||
'',
|
||||
options && options.temporaryReferences
|
||||
? options.temporaryReferences
|
||||
: undefined,
|
||||
resolve,
|
||||
reject,
|
||||
);
|
||||
if (options && options.signal) {
|
||||
const signal = options.signal;
|
||||
if (signal.aborted) {
|
||||
abort((signal: any).reason);
|
||||
} else {
|
||||
const listener = () => {
|
||||
abort((signal: any).reason);
|
||||
signal.removeEventListener('abort', listener);
|
||||
};
|
||||
signal.addEventListener('abort', listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export {createFromFetch, createFromReadableStream, encodeReply};
|
||||
136
packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientNode.js
vendored
Normal file
136
packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientNode.js
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
DebugChannel,
|
||||
FindSourceMapURLCallback,
|
||||
Response,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {
|
||||
ServerConsumerModuleMap,
|
||||
ModuleLoading,
|
||||
ServerManifest,
|
||||
} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
type ServerConsumerManifest = {
|
||||
moduleMap: ServerConsumerModuleMap,
|
||||
moduleLoading: ModuleLoading,
|
||||
serverModuleMap: null | ServerManifest,
|
||||
};
|
||||
|
||||
import type {Readable} from 'stream';
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
createStreamState,
|
||||
getRoot,
|
||||
reportGlobalError,
|
||||
processStringChunk,
|
||||
processBinaryChunk,
|
||||
close,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
export * from './ReactFlightDOMClientEdge';
|
||||
|
||||
function noServerCall() {
|
||||
throw new Error(
|
||||
'Server Functions cannot be called during initial render. ' +
|
||||
'This would create a fetch waterfall. Try to use a Server Component ' +
|
||||
'to pass data to Client Components instead.',
|
||||
);
|
||||
}
|
||||
|
||||
type EncodeFormActionCallback = <A>(
|
||||
id: any,
|
||||
args: Promise<A>,
|
||||
) => ReactCustomFormAction;
|
||||
|
||||
export type Options = {
|
||||
nonce?: string,
|
||||
encodeFormAction?: EncodeFormActionCallback,
|
||||
findSourceMapURL?: FindSourceMapURLCallback,
|
||||
replayConsoleLogs?: boolean,
|
||||
environmentName?: string,
|
||||
// For the Node.js client we only support a single-direction debug channel.
|
||||
debugChannel?: Readable,
|
||||
};
|
||||
|
||||
function startReadingFromStream(
|
||||
response: Response,
|
||||
stream: Readable,
|
||||
onEnd: () => void,
|
||||
): void {
|
||||
const streamState = createStreamState(response, stream);
|
||||
|
||||
stream.on('data', chunk => {
|
||||
if (typeof chunk === 'string') {
|
||||
processStringChunk(response, streamState, chunk);
|
||||
} else {
|
||||
processBinaryChunk(response, streamState, chunk);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', error => {
|
||||
reportGlobalError(response, error);
|
||||
});
|
||||
|
||||
stream.on('end', onEnd);
|
||||
}
|
||||
|
||||
function createFromNodeStream<T>(
|
||||
stream: Readable,
|
||||
serverConsumerManifest: ServerConsumerManifest,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const response: Response = createResponse(
|
||||
serverConsumerManifest.moduleMap,
|
||||
serverConsumerManifest.serverModuleMap,
|
||||
serverConsumerManifest.moduleLoading,
|
||||
noServerCall,
|
||||
options ? options.encodeFormAction : undefined,
|
||||
options && typeof options.nonce === 'string' ? options.nonce : undefined,
|
||||
undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
|
||||
__DEV__ && options && options.findSourceMapURL
|
||||
? options.findSourceMapURL
|
||||
: undefined,
|
||||
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
|
||||
if (__DEV__ && options && options.debugChannel) {
|
||||
let streamEndedCount = 0;
|
||||
const handleEnd = () => {
|
||||
if (++streamEndedCount === 2) {
|
||||
close(response);
|
||||
}
|
||||
};
|
||||
startReadingFromStream(response, options.debugChannel, handleEnd);
|
||||
startReadingFromStream(response, stream, handleEnd);
|
||||
} else {
|
||||
startReadingFromStream(response, stream, close.bind(null, response));
|
||||
}
|
||||
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
export {createFromNodeStream};
|
||||
701
packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js
vendored
Normal file
701
packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js
vendored
Normal file
@@ -0,0 +1,701 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
Request,
|
||||
ReactClientValue,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
import type {Destination} from 'react-server/src/ReactServerStreamConfigNode';
|
||||
import type {ClientManifest} from './ReactFlightServerConfigUnbundledBundler';
|
||||
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
|
||||
import type {Busboy} from 'busboy';
|
||||
import type {Writable} from 'stream';
|
||||
import type {Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import type {Duplex} from 'stream';
|
||||
|
||||
import {Readable} from 'stream';
|
||||
|
||||
import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
createPrerenderRequest,
|
||||
startWork,
|
||||
startFlowing,
|
||||
startFlowingDebug,
|
||||
stopFlowing,
|
||||
abort,
|
||||
resolveDebugMessage,
|
||||
closeDebugChannel,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
|
||||
import {
|
||||
createResponse,
|
||||
reportGlobalError,
|
||||
close,
|
||||
resolveField,
|
||||
resolveFile,
|
||||
resolveFileInfo,
|
||||
resolveFileChunk,
|
||||
resolveFileComplete,
|
||||
getRoot,
|
||||
} from 'react-server/src/ReactFlightReplyServer';
|
||||
|
||||
import {
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
} from 'react-server/src/ReactFlightActionServer';
|
||||
|
||||
export {
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
} from '../ReactFlightUnbundledReferences';
|
||||
|
||||
import {
|
||||
createStringDecoder,
|
||||
readPartialStringChunk,
|
||||
readFinalStringChunk,
|
||||
} from 'react-client/src/ReactFlightClientStreamConfigNode';
|
||||
|
||||
import {textEncoder} from 'react-server/src/ReactServerStreamConfigNode';
|
||||
|
||||
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
|
||||
|
||||
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
|
||||
|
||||
export type {TemporaryReferenceSet};
|
||||
|
||||
function createDrainHandler(destination: Destination, request: Request) {
|
||||
return () => startFlowing(request, destination);
|
||||
}
|
||||
|
||||
function createCancelHandler(request: Request, reason: string) {
|
||||
return () => {
|
||||
stopFlowing(request);
|
||||
abort(request, new Error(reason));
|
||||
};
|
||||
}
|
||||
|
||||
function startReadingFromDebugChannelReadable(
|
||||
request: Request,
|
||||
stream: Readable | WebSocket,
|
||||
): void {
|
||||
const stringDecoder = createStringDecoder();
|
||||
let lastWasPartial = false;
|
||||
let stringBuffer = '';
|
||||
function onData(chunk: string | Uint8Array) {
|
||||
if (typeof chunk === 'string') {
|
||||
if (lastWasPartial) {
|
||||
stringBuffer += readFinalStringChunk(stringDecoder, new Uint8Array(0));
|
||||
lastWasPartial = false;
|
||||
}
|
||||
stringBuffer += chunk;
|
||||
} else {
|
||||
const buffer: Uint8Array = (chunk: any);
|
||||
stringBuffer += readPartialStringChunk(stringDecoder, buffer);
|
||||
lastWasPartial = true;
|
||||
}
|
||||
const messages = stringBuffer.split('\n');
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
resolveDebugMessage(request, messages[i]);
|
||||
}
|
||||
stringBuffer = messages[messages.length - 1];
|
||||
}
|
||||
function onError(error: mixed) {
|
||||
abort(
|
||||
request,
|
||||
new Error('Lost connection to the Debug Channel.', {
|
||||
cause: error,
|
||||
}),
|
||||
);
|
||||
}
|
||||
function onClose() {
|
||||
closeDebugChannel(request);
|
||||
}
|
||||
if (
|
||||
// $FlowFixMe[method-unbinding]
|
||||
typeof stream.addEventListener === 'function' &&
|
||||
// $FlowFixMe[method-unbinding]
|
||||
typeof stream.binaryType === 'string'
|
||||
) {
|
||||
const ws: WebSocket = (stream: any);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.addEventListener('message', event => {
|
||||
// $FlowFixMe
|
||||
onData(event.data);
|
||||
});
|
||||
ws.addEventListener('error', event => {
|
||||
// $FlowFixMe
|
||||
onError(event.error);
|
||||
});
|
||||
ws.addEventListener('close', onClose);
|
||||
} else {
|
||||
const readable: Readable = (stream: any);
|
||||
readable.on('data', onData);
|
||||
readable.on('error', onError);
|
||||
readable.on('end', onClose);
|
||||
}
|
||||
}
|
||||
|
||||
type Options = {
|
||||
debugChannel?: Readable | Writable | Duplex | WebSocket,
|
||||
environmentName?: string | (() => string),
|
||||
filterStackFrame?: (url: string, functionName: string) => boolean,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
abort(reason: mixed): void,
|
||||
pipe<T: Writable>(destination: T): T,
|
||||
};
|
||||
|
||||
function renderToPipeableStream(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): PipeableStream {
|
||||
const debugChannel = __DEV__ && options ? options.debugChannel : undefined;
|
||||
const debugChannelReadable: void | Readable | WebSocket =
|
||||
__DEV__ &&
|
||||
debugChannel !== undefined &&
|
||||
// $FlowFixMe[method-unbinding]
|
||||
(typeof debugChannel.read === 'function' ||
|
||||
typeof debugChannel.readyState === 'number')
|
||||
? (debugChannel: any)
|
||||
: undefined;
|
||||
const debugChannelWritable: void | Writable =
|
||||
__DEV__ && debugChannel !== undefined
|
||||
? // $FlowFixMe[method-unbinding]
|
||||
typeof debugChannel.write === 'function'
|
||||
? (debugChannel: any)
|
||||
: // $FlowFixMe[method-unbinding]
|
||||
typeof debugChannel.send === 'function'
|
||||
? createFakeWritableFromWebSocket((debugChannel: any))
|
||||
: undefined
|
||||
: undefined;
|
||||
const request = createRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
);
|
||||
let hasStartedFlowing = false;
|
||||
startWork(request);
|
||||
if (debugChannelWritable !== undefined) {
|
||||
startFlowingDebug(request, debugChannelWritable);
|
||||
}
|
||||
if (debugChannelReadable !== undefined) {
|
||||
startReadingFromDebugChannelReadable(request, debugChannelReadable);
|
||||
}
|
||||
return {
|
||||
pipe<T: Writable>(destination: T): T {
|
||||
if (hasStartedFlowing) {
|
||||
throw new Error(
|
||||
'React currently only supports piping to one writable stream.',
|
||||
);
|
||||
}
|
||||
hasStartedFlowing = true;
|
||||
startFlowing(request, destination);
|
||||
destination.on('drain', createDrainHandler(destination, request));
|
||||
destination.on(
|
||||
'error',
|
||||
createCancelHandler(
|
||||
request,
|
||||
'The destination stream errored while writing data.',
|
||||
),
|
||||
);
|
||||
// We don't close until the debug channel closes.
|
||||
if (!__DEV__ || debugChannelReadable === undefined) {
|
||||
destination.on(
|
||||
'close',
|
||||
createCancelHandler(request, 'The destination stream closed early.'),
|
||||
);
|
||||
}
|
||||
return destination;
|
||||
},
|
||||
abort(reason: mixed) {
|
||||
abort(request, reason);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable {
|
||||
return ({
|
||||
write(chunk: string | Uint8Array) {
|
||||
webSocket.send((chunk: any));
|
||||
return true;
|
||||
},
|
||||
end() {
|
||||
webSocket.close();
|
||||
},
|
||||
destroy(reason) {
|
||||
if (typeof reason === 'object' && reason !== null) {
|
||||
reason = reason.message;
|
||||
}
|
||||
if (typeof reason === 'string') {
|
||||
webSocket.close(1011, reason);
|
||||
} else {
|
||||
webSocket.close(1011);
|
||||
}
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
function createFakeWritableFromReadableStreamController(
|
||||
controller: ReadableStreamController,
|
||||
): Writable {
|
||||
// The current host config expects a Writable so we create
|
||||
// a fake writable for now to push into the Readable.
|
||||
return ({
|
||||
write(chunk: string | Uint8Array) {
|
||||
if (typeof chunk === 'string') {
|
||||
chunk = textEncoder.encode(chunk);
|
||||
}
|
||||
controller.enqueue(chunk);
|
||||
// in web streams there is no backpressure so we can always write more
|
||||
return true;
|
||||
},
|
||||
end() {
|
||||
controller.close();
|
||||
},
|
||||
destroy(error) {
|
||||
// $FlowFixMe[method-unbinding]
|
||||
if (typeof controller.error === 'function') {
|
||||
// $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
|
||||
controller.error(error);
|
||||
} else {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
request: Request,
|
||||
stream: ReadableStream,
|
||||
): void {
|
||||
const reader = stream.getReader();
|
||||
const stringDecoder = createStringDecoder();
|
||||
let stringBuffer = '';
|
||||
function progress({
|
||||
done,
|
||||
value,
|
||||
}: {
|
||||
done: boolean,
|
||||
value: ?any,
|
||||
...
|
||||
}): void | Promise<void> {
|
||||
const buffer: Uint8Array = (value: any);
|
||||
stringBuffer += done
|
||||
? readFinalStringChunk(stringDecoder, new Uint8Array(0))
|
||||
: readPartialStringChunk(stringDecoder, buffer);
|
||||
const messages = stringBuffer.split('\n');
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
resolveDebugMessage(request, messages[i]);
|
||||
}
|
||||
stringBuffer = messages[messages.length - 1];
|
||||
if (done) {
|
||||
closeDebugChannel(request);
|
||||
return;
|
||||
}
|
||||
return reader.read().then(progress).catch(error);
|
||||
}
|
||||
function error(e: any) {
|
||||
abort(
|
||||
request,
|
||||
new Error('Lost connection to the Debug Channel.', {
|
||||
cause: e,
|
||||
}),
|
||||
);
|
||||
}
|
||||
reader.read().then(progress).catch(error);
|
||||
}
|
||||
|
||||
function renderToReadableStream(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: Omit<Options, 'debugChannel'> & {
|
||||
debugChannel?: {readable?: ReadableStream, writable?: WritableStream, ...},
|
||||
signal?: AbortSignal,
|
||||
},
|
||||
): ReadableStream {
|
||||
const debugChannelReadable =
|
||||
__DEV__ && options && options.debugChannel
|
||||
? options.debugChannel.readable
|
||||
: undefined;
|
||||
const debugChannelWritable =
|
||||
__DEV__ && options && options.debugChannel
|
||||
? options.debugChannel.writable
|
||||
: undefined;
|
||||
const request = createRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
);
|
||||
if (options && options.signal) {
|
||||
const signal = options.signal;
|
||||
if (signal.aborted) {
|
||||
abort(request, (signal: any).reason);
|
||||
} else {
|
||||
const listener = () => {
|
||||
abort(request, (signal: any).reason);
|
||||
signal.removeEventListener('abort', listener);
|
||||
};
|
||||
signal.addEventListener('abort', listener);
|
||||
}
|
||||
}
|
||||
if (debugChannelWritable !== undefined) {
|
||||
let debugWritable: Writable;
|
||||
const debugStream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
debugWritable =
|
||||
createFakeWritableFromReadableStreamController(controller);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowingDebug(request, debugWritable);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
debugStream.pipeTo(debugChannelWritable);
|
||||
}
|
||||
if (debugChannelReadable !== undefined) {
|
||||
startReadingFromDebugChannelReadableStream(request, debugChannelReadable);
|
||||
}
|
||||
let writable: Writable;
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
writable = createFakeWritableFromReadableStreamController(controller);
|
||||
startWork(request);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
cancel: (reason): ?Promise<void> => {
|
||||
stopFlowing(request);
|
||||
abort(request, reason);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
return stream;
|
||||
}
|
||||
|
||||
function createFakeWritableFromNodeReadable(readable: any): Writable {
|
||||
// The current host config expects a Writable so we create
|
||||
// a fake writable for now to push into the Readable.
|
||||
return ({
|
||||
write(chunk: string | Uint8Array) {
|
||||
return readable.push(chunk);
|
||||
},
|
||||
end() {
|
||||
readable.push(null);
|
||||
},
|
||||
destroy(error) {
|
||||
readable.destroy(error);
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
type PrerenderOptions = {
|
||||
environmentName?: string | (() => string),
|
||||
filterStackFrame?: (url: string, functionName: string) => boolean,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
prelude: Readable,
|
||||
};
|
||||
|
||||
function prerenderToNodeStream(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: PrerenderOptions,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const readable: Readable = new Readable({
|
||||
read() {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
});
|
||||
const writable = createFakeWritableFromNodeReadable(readable);
|
||||
resolve({prelude: readable});
|
||||
}
|
||||
|
||||
const request = createPrerenderRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
);
|
||||
if (options && options.signal) {
|
||||
const signal = options.signal;
|
||||
if (signal.aborted) {
|
||||
const reason = (signal: any).reason;
|
||||
abort(request, reason);
|
||||
} else {
|
||||
const listener = () => {
|
||||
const reason = (signal: any).reason;
|
||||
abort(request, reason);
|
||||
signal.removeEventListener('abort', listener);
|
||||
};
|
||||
signal.addEventListener('abort', listener);
|
||||
}
|
||||
}
|
||||
startWork(request);
|
||||
});
|
||||
}
|
||||
|
||||
function prerender(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: Options & {
|
||||
signal?: AbortSignal,
|
||||
},
|
||||
): Promise<{
|
||||
prelude: ReadableStream,
|
||||
}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
let writable: Writable;
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
writable =
|
||||
createFakeWritableFromReadableStreamController(controller);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
cancel: (reason): ?Promise<void> => {
|
||||
stopFlowing(request);
|
||||
abort(request, reason);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
resolve({prelude: stream});
|
||||
}
|
||||
const request = createPrerenderRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
);
|
||||
if (options && options.signal) {
|
||||
const signal = options.signal;
|
||||
if (signal.aborted) {
|
||||
const reason = (signal: any).reason;
|
||||
abort(request, reason);
|
||||
} else {
|
||||
const listener = () => {
|
||||
const reason = (signal: any).reason;
|
||||
abort(request, reason);
|
||||
signal.removeEventListener('abort', listener);
|
||||
};
|
||||
signal.addEventListener('abort', listener);
|
||||
}
|
||||
}
|
||||
startWork(request);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReplyFromBusboy<T>(
|
||||
busboyStream: Busboy,
|
||||
webpackMap: ServerManifest,
|
||||
options?: {temporaryReferences?: TemporaryReferenceSet},
|
||||
): Thenable<T> {
|
||||
const response = createResponse(
|
||||
webpackMap,
|
||||
'',
|
||||
options ? options.temporaryReferences : undefined,
|
||||
);
|
||||
let pendingFiles = 0;
|
||||
const queuedFields: Array<string> = [];
|
||||
busboyStream.on('field', (name, value) => {
|
||||
if (pendingFiles > 0) {
|
||||
// Because the 'end' event fires two microtasks after the next 'field'
|
||||
// we would resolve files and fields out of order. To handle this properly
|
||||
// we queue any fields we receive until the previous file is done.
|
||||
queuedFields.push(name, value);
|
||||
} else {
|
||||
try {
|
||||
resolveField(response, name, value);
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
|
||||
if (encoding.toLowerCase() === 'base64') {
|
||||
busboyStream.destroy(
|
||||
new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
pendingFiles++;
|
||||
const file = resolveFileInfo(response, name, filename, mimeType);
|
||||
value.on('data', chunk => {
|
||||
resolveFileChunk(response, file, chunk);
|
||||
});
|
||||
value.on('end', () => {
|
||||
try {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
busboyStream.on('finish', () => {
|
||||
close(response);
|
||||
});
|
||||
busboyStream.on('error', err => {
|
||||
reportGlobalError(
|
||||
response,
|
||||
// $FlowFixMe[incompatible-call] types Error and mixed are incompatible
|
||||
err,
|
||||
);
|
||||
});
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
webpackMap: ServerManifest,
|
||||
options?: {temporaryReferences?: TemporaryReferenceSet},
|
||||
): Thenable<T> {
|
||||
if (typeof body === 'string') {
|
||||
const form = new FormData();
|
||||
form.append('0', body);
|
||||
body = form;
|
||||
}
|
||||
const response = createResponse(
|
||||
webpackMap,
|
||||
'',
|
||||
options ? options.temporaryReferences : undefined,
|
||||
body,
|
||||
);
|
||||
const root = getRoot<T>(response);
|
||||
close(response);
|
||||
return root;
|
||||
}
|
||||
|
||||
function decodeReplyFromAsyncIterable<T>(
|
||||
iterable: AsyncIterable<[string, string | File]>,
|
||||
webpackMap: ServerManifest,
|
||||
options?: {temporaryReferences?: TemporaryReferenceSet},
|
||||
): Thenable<T> {
|
||||
const iterator: AsyncIterator<[string, string | File]> =
|
||||
iterable[ASYNC_ITERATOR]();
|
||||
|
||||
const response = createResponse(
|
||||
webpackMap,
|
||||
'',
|
||||
options ? options.temporaryReferences : undefined,
|
||||
);
|
||||
|
||||
function progress(
|
||||
entry:
|
||||
| {done: false, +value: [string, string | File], ...}
|
||||
| {done: true, +value: void, ...},
|
||||
) {
|
||||
if (entry.done) {
|
||||
close(response);
|
||||
} else {
|
||||
const [name, value] = entry.value;
|
||||
if (typeof value === 'string') {
|
||||
resolveField(response, name, value);
|
||||
} else {
|
||||
resolveFile(response, name, value);
|
||||
}
|
||||
iterator.next().then(progress, error);
|
||||
}
|
||||
}
|
||||
function error(reason: Error) {
|
||||
reportGlobalError(response, reason);
|
||||
if (typeof (iterator: any).throw === 'function') {
|
||||
// The iterator protocol doesn't necessarily include this but a generator do.
|
||||
// $FlowFixMe should be able to pass mixed
|
||||
iterator.throw(reason).then(error, error);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.next().then(progress, error);
|
||||
|
||||
return getRoot(response);
|
||||
}
|
||||
|
||||
export {
|
||||
renderToReadableStream,
|
||||
renderToPipeableStream,
|
||||
prerender,
|
||||
prerenderToNodeStream,
|
||||
decodeReply,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReplyFromAsyncIterable,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
};
|
||||
108
packages/react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler.js
vendored
Normal file
108
packages/react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler.js
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
|
||||
import type {
|
||||
ImportMetadata,
|
||||
ImportManifestEntry,
|
||||
} from '../shared/ReactFlightImportMetadata';
|
||||
|
||||
import type {
|
||||
ClientReference,
|
||||
ServerReference,
|
||||
} from '../ReactFlightUnbundledReferences';
|
||||
|
||||
export type {ClientReference, ServerReference};
|
||||
|
||||
export type ClientManifest = {
|
||||
[id: string]: ClientReferenceManifestEntry,
|
||||
};
|
||||
|
||||
export type ServerReferenceId = string;
|
||||
|
||||
export type ClientReferenceMetadata = ImportMetadata;
|
||||
export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
|
||||
|
||||
export type ClientReferenceKey = string;
|
||||
|
||||
export {
|
||||
isClientReference,
|
||||
isServerReference,
|
||||
} from '../ReactFlightUnbundledReferences';
|
||||
|
||||
export function getClientReferenceKey(
|
||||
reference: ClientReference<any>,
|
||||
): ClientReferenceKey {
|
||||
return reference.$$async ? reference.$$id + '#async' : reference.$$id;
|
||||
}
|
||||
|
||||
export function resolveClientReferenceMetadata<T>(
|
||||
config: ClientManifest,
|
||||
clientReference: ClientReference<T>,
|
||||
): ClientReferenceMetadata {
|
||||
const modulePath = clientReference.$$id;
|
||||
let name = '';
|
||||
let resolvedModuleData = config[modulePath];
|
||||
if (resolvedModuleData) {
|
||||
// The potentially aliased name.
|
||||
name = resolvedModuleData.name;
|
||||
} else {
|
||||
// We didn't find this specific export name but we might have the * export
|
||||
// which contains this name as well.
|
||||
// TODO: It's unfortunate that we now have to parse this string. We should
|
||||
// probably go back to encoding path and name separately on the client reference.
|
||||
const idx = modulePath.lastIndexOf('#');
|
||||
if (idx !== -1) {
|
||||
name = modulePath.slice(idx + 1);
|
||||
resolvedModuleData = config[modulePath.slice(0, idx)];
|
||||
}
|
||||
if (!resolvedModuleData) {
|
||||
throw new Error(
|
||||
'Could not find the module "' +
|
||||
modulePath +
|
||||
'" in the React Client Manifest. ' +
|
||||
'This is probably a bug in the React Server Components bundler.',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (resolvedModuleData.async === true && clientReference.$$async === true) {
|
||||
throw new Error(
|
||||
'The module "' +
|
||||
modulePath +
|
||||
'" is marked as an async ESM module but was loaded as a CJS proxy. ' +
|
||||
'This is probably a bug in the React Server Components bundler.',
|
||||
);
|
||||
}
|
||||
if (resolvedModuleData.async === true || clientReference.$$async === true) {
|
||||
return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
|
||||
} else {
|
||||
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
|
||||
}
|
||||
}
|
||||
|
||||
export function getServerReferenceId<T>(
|
||||
config: ClientManifest,
|
||||
serverReference: ServerReference<T>,
|
||||
): ServerReferenceId {
|
||||
return serverReference.$$id;
|
||||
}
|
||||
|
||||
export function getServerReferenceBoundArguments<T>(
|
||||
config: ClientManifest,
|
||||
serverReference: ServerReference<T>,
|
||||
): null | Array<ReactClientValue> {
|
||||
return serverReference.$$bound;
|
||||
}
|
||||
|
||||
export function getServerReferenceLocation<T>(
|
||||
config: ClientManifest,
|
||||
serverReference: ServerReference<T>,
|
||||
): void | Error {
|
||||
return serverReference.$$location;
|
||||
}
|
||||
44
packages/react-server-dom-unbundled/src/shared/ReactFlightImportMetadata.js
vendored
Normal file
44
packages/react-server-dom-unbundled/src/shared/ReactFlightImportMetadata.js
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type ImportManifestEntry = {
|
||||
id: string,
|
||||
// chunks is a double indexed array of chunkId / chunkFilename pairs
|
||||
chunks: Array<string>,
|
||||
name: string,
|
||||
async?: boolean,
|
||||
};
|
||||
|
||||
// This is the parsed shape of the wire format which is why it is
|
||||
// condensed to only the essentialy information
|
||||
export type ImportMetadata =
|
||||
| [
|
||||
/* id */ string,
|
||||
/* chunks id/filename pairs, double indexed */ Array<string>,
|
||||
/* name */ string,
|
||||
/* async */ 1,
|
||||
]
|
||||
| [
|
||||
/* id */ string,
|
||||
/* chunks id/filename pairs, double indexed */ Array<string>,
|
||||
/* name */ string,
|
||||
];
|
||||
|
||||
export const ID = 0;
|
||||
export const CHUNKS = 1;
|
||||
export const NAME = 2;
|
||||
// export const ASYNC = 3;
|
||||
|
||||
// This logic is correct because currently only include the 4th tuple member
|
||||
// when the module is async. If that changes we will need to actually assert
|
||||
// the value is true. We don't index into the 4th slot because flow does not
|
||||
// like the potential out of bounds access
|
||||
export function isAsyncImport(metadata: ImportMetadata): boolean {
|
||||
return metadata.length === 4;
|
||||
}
|
||||
13
packages/react-server-dom-unbundled/static.js
vendored
Normal file
13
packages/react-server-dom-unbundled/static.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
throw new Error(
|
||||
'The React Server cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.',
|
||||
);
|
||||
@@ -10,4 +10,4 @@
|
||||
export {
|
||||
prerender,
|
||||
prerenderToNodeStream,
|
||||
} from './src/server/react-flight-dom-server.node.unbundled';
|
||||
} from './src/server/react-flight-dom-server.node';
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-server-dom-webpack",
|
||||
"description": "React Server Components bindings for DOM using Webpack. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -17,17 +17,14 @@
|
||||
"client.browser.js",
|
||||
"client.edge.js",
|
||||
"client.node.js",
|
||||
"client.node.unbundled.js",
|
||||
"server.js",
|
||||
"server.browser.js",
|
||||
"server.edge.js",
|
||||
"server.node.js",
|
||||
"server.node.unbundled.js",
|
||||
"static.js",
|
||||
"static.browser.js",
|
||||
"static.edge.js",
|
||||
"static.node.js",
|
||||
"static.node.unbundled.js",
|
||||
"node-register.js",
|
||||
"cjs/",
|
||||
"esm/"
|
||||
@@ -39,10 +36,7 @@
|
||||
"workerd": "./client.edge.js",
|
||||
"deno": "./client.edge.js",
|
||||
"worker": "./client.edge.js",
|
||||
"node": {
|
||||
"webpack": "./client.node.js",
|
||||
"default": "./client.node.unbundled.js"
|
||||
},
|
||||
"node": "./client.node.js",
|
||||
"edge-light": "./client.edge.js",
|
||||
"browser": "./client.browser.js",
|
||||
"default": "./client.browser.js"
|
||||
@@ -50,15 +44,11 @@
|
||||
"./client.browser": "./client.browser.js",
|
||||
"./client.edge": "./client.edge.js",
|
||||
"./client.node": "./client.node.js",
|
||||
"./client.node.unbundled": "./client.node.unbundled.js",
|
||||
"./server": {
|
||||
"react-server": {
|
||||
"workerd": "./server.edge.js",
|
||||
"deno": "./server.browser.js",
|
||||
"node": {
|
||||
"webpack": "./server.node.js",
|
||||
"default": "./server.node.unbundled.js"
|
||||
},
|
||||
"node": "./server.node.js",
|
||||
"edge-light": "./server.edge.js",
|
||||
"browser": "./server.browser.js"
|
||||
},
|
||||
@@ -67,15 +57,11 @@
|
||||
"./server.browser": "./server.browser.js",
|
||||
"./server.edge": "./server.edge.js",
|
||||
"./server.node": "./server.node.js",
|
||||
"./server.node.unbundled": "./server.node.unbundled.js",
|
||||
"./static": {
|
||||
"react-server": {
|
||||
"workerd": "./static.edge.js",
|
||||
"deno": "./static.browser.js",
|
||||
"node": {
|
||||
"webpack": "./static.node.js",
|
||||
"default": "./static.node.unbundled.js"
|
||||
},
|
||||
"node": "./static.node.js",
|
||||
"edge-light": "./static.edge.js",
|
||||
"browser": "./static.browser.js"
|
||||
},
|
||||
@@ -84,7 +70,6 @@
|
||||
"./static.browser": "./static.browser.js",
|
||||
"./static.edge": "./static.edge.js",
|
||||
"./static.node": "./static.node.js",
|
||||
"./static.node.unbundled": "./static.node.unbundled.js",
|
||||
"./node-loader": "./esm/react-server-dom-webpack-node-loader.production.js",
|
||||
"./node-register": "./node-register.js",
|
||||
"./src/*": "./src/*.js",
|
||||
@@ -100,8 +85,8 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react": "^19.2.2",
|
||||
"react-dom": "^19.2.2",
|
||||
"webpack": "^5.59.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -102,6 +102,12 @@ function bind(this: ServerReference<any>): any {
|
||||
return newFn;
|
||||
}
|
||||
|
||||
const serverReferenceToString = {
|
||||
value: () => 'function () { [omitted code] }',
|
||||
configurable: true,
|
||||
writable: true,
|
||||
};
|
||||
|
||||
export function registerServerReference<T: Function>(
|
||||
reference: T,
|
||||
id: string,
|
||||
@@ -125,12 +131,14 @@ export function registerServerReference<T: Function>(
|
||||
configurable: true,
|
||||
},
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
} as PropertyDescriptorMap)
|
||||
: ({
|
||||
$$typeof,
|
||||
$$id,
|
||||
$$bound,
|
||||
bind: {value: bind, configurable: true},
|
||||
toString: serverReferenceToString,
|
||||
} as PropertyDescriptorMap),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ describe('ReactFlightDOM', () => {
|
||||
FlightReactDOM = require('react-dom');
|
||||
|
||||
jest.mock('react-server-dom-webpack/server', () =>
|
||||
require('react-server-dom-webpack/server.node.unbundled'),
|
||||
require('react-server-dom-unbundled/server.node'),
|
||||
);
|
||||
jest.mock('react-server-dom-webpack/static', () =>
|
||||
require('react-server-dom-webpack/static.node.unbundled'),
|
||||
require('react-server-dom-unbundled/static.node'),
|
||||
);
|
||||
const WebpackMock = require('./utils/WebpackMock');
|
||||
clientExports = WebpackMock.clientExports;
|
||||
@@ -158,6 +158,23 @@ describe('ReactFlightDOM', () => {
|
||||
};
|
||||
}
|
||||
|
||||
function createUnclosingStream(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ReadableStream<Uint8Array> {
|
||||
const reader = stream.getReader();
|
||||
|
||||
const s = new ReadableStream({
|
||||
async pull(controller) {
|
||||
const {done, value} = await reader.read();
|
||||
if (!done) {
|
||||
controller.enqueue(value);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
const theInfinitePromise = new Promise(() => {});
|
||||
function InfiniteSuspend() {
|
||||
throw theInfinitePromise;
|
||||
@@ -3055,7 +3072,7 @@ describe('ReactFlightDOM', () => {
|
||||
const {prelude} = await pendingResult;
|
||||
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
Readable.toWeb(prelude),
|
||||
createUnclosingStream(Readable.toWeb(prelude)),
|
||||
);
|
||||
|
||||
const iterator = result.multiShotIterable[Symbol.asyncIterator]();
|
||||
|
||||
@@ -2926,15 +2926,16 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const app = ReactServer.createElement(
|
||||
ReactServer.Fragment,
|
||||
null,
|
||||
ReactServer.createElement(Paragraph, null, 'foo'),
|
||||
ReactServer.createElement(Paragraph, null, 'bar'),
|
||||
);
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
{
|
||||
root: ReactServer.createElement(
|
||||
ReactServer.Fragment,
|
||||
null,
|
||||
ReactServer.createElement(Paragraph, null, 'foo'),
|
||||
ReactServer.createElement(Paragraph, null, 'bar'),
|
||||
),
|
||||
root: app,
|
||||
},
|
||||
webpackMap,
|
||||
{
|
||||
@@ -3006,27 +3007,11 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
"name": "Paragraph",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"",
|
||||
"/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js",
|
||||
2935,
|
||||
27,
|
||||
2929,
|
||||
34,
|
||||
],
|
||||
[
|
||||
"serverAct",
|
||||
"/packages/internal-test-utils/internalAct.js",
|
||||
270,
|
||||
19,
|
||||
231,
|
||||
1,
|
||||
],
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js",
|
||||
2929,
|
||||
18,
|
||||
2932,
|
||||
19,
|
||||
2916,
|
||||
89,
|
||||
],
|
||||
|
||||
@@ -232,7 +232,7 @@ describe('ReactFlightDOMEdge', () => {
|
||||
|
||||
async function createBufferedUnclosingStream(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ReadableStream<Uint8Array> {
|
||||
): Promise<ReadableStream<Uint8Array>> {
|
||||
const chunks: Array<Uint8Array> = [];
|
||||
const reader = stream.getReader();
|
||||
while (true) {
|
||||
@@ -2201,4 +2201,59 @@ describe('ReactFlightDOMEdge', () => {
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
|
||||
it('should properly resolve with deduped objects', async () => {
|
||||
const obj = {foo: 'hi'};
|
||||
|
||||
function Test(props) {
|
||||
return props.obj.foo;
|
||||
}
|
||||
|
||||
const root = {
|
||||
obj: obj,
|
||||
node: <Test obj={obj} />,
|
||||
};
|
||||
|
||||
const stream = ReactServerDOMServer.renderToReadableStream(root);
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
serverConsumerManifest: {
|
||||
moduleMap: null,
|
||||
moduleLoading: null,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response;
|
||||
expect(result).toEqual({obj: obj, node: 'hi'});
|
||||
});
|
||||
|
||||
it('does not leak the server reference code', async () => {
|
||||
function foo() {
|
||||
return 'foo';
|
||||
}
|
||||
|
||||
const bar = () => {
|
||||
return 'bar';
|
||||
};
|
||||
|
||||
const anonymous = (
|
||||
() => () =>
|
||||
'anonymous'
|
||||
)();
|
||||
|
||||
expect(
|
||||
ReactServerDOMServer.registerServerReference(foo, 'foo-id').toString(),
|
||||
).toBe('function () { [omitted code] }');
|
||||
|
||||
expect(
|
||||
ReactServerDOMServer.registerServerReference(bar, 'bar-id').toString(),
|
||||
).toBe('function () { [omitted code] }');
|
||||
|
||||
expect(
|
||||
ReactServerDOMServer.registerServerReference(
|
||||
anonymous,
|
||||
'anonymous-id',
|
||||
).toString(),
|
||||
).toBe('function () { [omitted code] }');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,12 +45,12 @@ describe('ReactFlightDOMNode', () => {
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-webpack/server', () =>
|
||||
require('react-server-dom-webpack/server.node'),
|
||||
jest.requireActual('react-server-dom-webpack/server.node'),
|
||||
);
|
||||
ReactServer = require('react');
|
||||
ReactServerDOMServer = require('react-server-dom-webpack/server');
|
||||
jest.mock('react-server-dom-webpack/static', () =>
|
||||
require('react-server-dom-webpack/static.node'),
|
||||
jest.requireActual('react-server-dom-webpack/static.node'),
|
||||
);
|
||||
ReactServerDOMStaticServer = require('react-server-dom-webpack/static');
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('ReactFlightDOMNode', () => {
|
||||
__unmockReact();
|
||||
jest.unmock('react-server-dom-webpack/server');
|
||||
jest.mock('react-server-dom-webpack/client', () =>
|
||||
require('react-server-dom-webpack/client.node'),
|
||||
jest.requireActual('react-server-dom-webpack/client.node'),
|
||||
);
|
||||
|
||||
React = require('react');
|
||||
@@ -127,7 +127,7 @@ describe('ReactFlightDOMNode', () => {
|
||||
|
||||
async function createBufferedUnclosingStream(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ReadableStream<Uint8Array> {
|
||||
): Promise<ReadableStream<Uint8Array>> {
|
||||
const chunks: Array<Uint8Array> = [];
|
||||
const reader = stream.getReader();
|
||||
while (true) {
|
||||
@@ -394,7 +394,7 @@ describe('ReactFlightDOMNode', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancels the underlying ReadableStream when we are cancelled', async () => {
|
||||
it('should cancel the underlying and transported ReadableStreams when we are cancelled', async () => {
|
||||
let controller;
|
||||
let cancelReason;
|
||||
const s = new ReadableStream({
|
||||
@@ -418,16 +418,30 @@ describe('ReactFlightDOMNode', () => {
|
||||
),
|
||||
);
|
||||
|
||||
const writable = new Stream.PassThrough(streamOptions);
|
||||
rscStream.pipe(writable);
|
||||
const readable = new Stream.PassThrough(streamOptions);
|
||||
rscStream.pipe(readable);
|
||||
|
||||
const result = await ReactServerDOMClient.createFromNodeStream(readable, {
|
||||
moduleMap: {},
|
||||
moduleLoading: webpackModuleLoading,
|
||||
});
|
||||
const reader = result.getReader();
|
||||
|
||||
controller.enqueue('hi');
|
||||
|
||||
await serverAct(async () => {
|
||||
// We should be able to read the part we already emitted before the abort
|
||||
expect(await reader.read()).toEqual({
|
||||
value: 'hi',
|
||||
done: false,
|
||||
});
|
||||
});
|
||||
|
||||
const reason = new Error('aborted');
|
||||
writable.destroy(reason);
|
||||
readable.destroy(reason);
|
||||
|
||||
await new Promise(resolve => {
|
||||
writable.on('error', () => {
|
||||
readable.on('error', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -435,9 +449,17 @@ describe('ReactFlightDOMNode', () => {
|
||||
expect(cancelReason.message).toBe(
|
||||
'The destination stream errored while writing data.',
|
||||
);
|
||||
|
||||
let error = null;
|
||||
try {
|
||||
await reader.read();
|
||||
} catch (x) {
|
||||
error = x;
|
||||
}
|
||||
expect(error).toBe(reason);
|
||||
});
|
||||
|
||||
it('should cancels the underlying ReadableStream when we abort', async () => {
|
||||
it('should cancel the underlying and transported ReadableStreams when we abort', async () => {
|
||||
const errors = [];
|
||||
let controller;
|
||||
let cancelReason;
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('ReactFlightDOMReplyEdge', () => {
|
||||
expect(await resultBlob.arrayBuffer()).toEqual(await blob.arrayBuffer());
|
||||
});
|
||||
|
||||
it('should supports ReadableStreams with typed arrays', async () => {
|
||||
it('should support ReadableStreams with typed arrays', async () => {
|
||||
const buffer = new Uint8Array([
|
||||
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
|
||||
]).buffer;
|
||||
@@ -246,6 +246,53 @@ describe('ReactFlightDOMReplyEdge', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancel the transported ReadableStream when we are cancelled', async () => {
|
||||
const s = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue('hi');
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const body = await ReactServerDOMClient.encodeReply(s);
|
||||
|
||||
const iterable = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const entry of body) {
|
||||
if (entry[1] === 'C') {
|
||||
// Return before finishing the stream.
|
||||
return;
|
||||
}
|
||||
yield entry;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const result = await ReactServerDOMServer.decodeReplyFromAsyncIterable(
|
||||
iterable,
|
||||
webpackServerMap,
|
||||
);
|
||||
|
||||
const reader = result.getReader();
|
||||
|
||||
// We should be able to read the part we already emitted before the abort
|
||||
expect(await reader.read()).toEqual({
|
||||
value: 'hi',
|
||||
done: false,
|
||||
});
|
||||
|
||||
let error = null;
|
||||
try {
|
||||
await reader.read();
|
||||
} catch (x) {
|
||||
error = x;
|
||||
}
|
||||
|
||||
expect(error).not.toBe(null);
|
||||
expect(error.message).toBe('Connection closed.');
|
||||
});
|
||||
|
||||
it('should abort when parsing an incomplete payload', async () => {
|
||||
const infinitePromise = new Promise(() => {});
|
||||
const controller = new AbortController();
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
addChunkDebugInfo,
|
||||
} from 'react-client/src/ReactFlightClientConfig';
|
||||
|
||||
import hasOwnProperty from 'shared/hasOwnProperty';
|
||||
|
||||
export type ServerConsumerModuleMap = null | {
|
||||
[clientId: string]: {
|
||||
[clientExportName: string]: ClientReferenceManifestEntry,
|
||||
@@ -253,7 +255,10 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
|
||||
// default property of this if it was an ESM interop module.
|
||||
return moduleExports.__esModule ? moduleExports.default : moduleExports;
|
||||
}
|
||||
return moduleExports[metadata[NAME]];
|
||||
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
|
||||
return moduleExports[metadata[NAME]];
|
||||
}
|
||||
return (undefined: any);
|
||||
}
|
||||
|
||||
export function getModuleDebugInfo<T>(
|
||||
|
||||
@@ -570,16 +570,23 @@ function decodeReplyFromBusboy<T>(
|
||||
// we queue any fields we receive until the previous file is done.
|
||||
queuedFields.push(name, value);
|
||||
} else {
|
||||
resolveField(response, name, value);
|
||||
try {
|
||||
resolveField(response, name, value);
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
|
||||
if (encoding.toLowerCase() === 'base64') {
|
||||
throw new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
busboyStream.destroy(
|
||||
new Error(
|
||||
"React doesn't accept base64 encoded file uploads because we don't expect " +
|
||||
"form data passed from a browser to ever encode data that way. If that's " +
|
||||
'the wrong assumption, we can easily fix it.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
pendingFiles++;
|
||||
const file = resolveFileInfo(response, name, filename, mimeType);
|
||||
@@ -587,14 +594,18 @@ function decodeReplyFromBusboy<T>(
|
||||
resolveFileChunk(response, file, chunk);
|
||||
});
|
||||
value.on('end', () => {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
try {
|
||||
resolveFileComplete(response, name, file);
|
||||
pendingFiles--;
|
||||
if (pendingFiles === 0) {
|
||||
// Release any queued fields
|
||||
for (let i = 0; i < queuedFields.length; i += 2) {
|
||||
resolveField(response, queuedFields[i], queuedFields[i + 1]);
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
}
|
||||
queuedFields.length = 0;
|
||||
} catch (error) {
|
||||
busboyStream.destroy(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
831
packages/react-server/src/ReactFlightReplyServer.js
vendored
831
packages/react-server/src/ReactFlightReplyServer.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -2672,7 +2672,7 @@ function serializePromiseID(id: number): string {
|
||||
}
|
||||
|
||||
function serializeServerReferenceID(id: number): string {
|
||||
return '$F' + id.toString(16);
|
||||
return '$h' + id.toString(16);
|
||||
}
|
||||
|
||||
function serializeSymbolReference(name: string): string {
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
jest.mock('react-server-dom-webpack/server', () =>
|
||||
require('react-server-dom-webpack/server.node'),
|
||||
jest.requireActual('react-server-dom-webpack/server.node'),
|
||||
);
|
||||
ReactServer = require('react');
|
||||
ReactServerDOMServer = require('react-server-dom-webpack/server');
|
||||
@@ -51,7 +51,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
__unmockReact();
|
||||
jest.unmock('react-server-dom-webpack/server');
|
||||
jest.mock('react-server-dom-webpack/client', () =>
|
||||
require('react-server-dom-webpack/client.node'),
|
||||
jest.requireActual('react-server-dom-webpack/client.node'),
|
||||
);
|
||||
|
||||
React = require('react');
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {AsyncLocalStorage} from 'async_hooks';
|
||||
|
||||
import type {Request} from 'react-server/src/ReactFlightServer';
|
||||
import type {ReactComponentInfo} from 'shared/ReactTypes';
|
||||
|
||||
export * from 'react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler';
|
||||
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
|
||||
|
||||
export const supportsRequestStorage = true;
|
||||
export const requestStorage: AsyncLocalStorage<Request | void> =
|
||||
new AsyncLocalStorage();
|
||||
|
||||
export const supportsComponentStorage = __DEV__;
|
||||
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
|
||||
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
|
||||
|
||||
export * from '../ReactFlightServerConfigDebugNode';
|
||||
|
||||
export * from '../ReactFlightStackConfigV8';
|
||||
export * from '../ReactServerConsoleConfigServer';
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-test-renderer",
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"description": "React package for snapshot testing.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -19,11 +19,11 @@
|
||||
},
|
||||
"homepage": "https://react.dev/",
|
||||
"dependencies": {
|
||||
"react-is": "^19.2.0",
|
||||
"react-is": "^19.2.2",
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0"
|
||||
"react": "^19.2.2"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
"version": "19.2.0",
|
||||
"version": "19.2.2",
|
||||
"homepage": "https://react.dev/",
|
||||
"bugs": "https://github.com/facebook/react/issues",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
// TODO: This module is used both by the release scripts and to expose a version
|
||||
// at runtime. We should instead inject the version number as part of the build
|
||||
// process, and use the ReactVersions.js module as the single source of truth.
|
||||
export default '19.2.0';
|
||||
export default '19.2.2';
|
||||
|
||||
@@ -551,5 +551,9 @@
|
||||
"563": "This render completed successfully. All cacheSignals are now aborted to allow clean up of any unused resources.",
|
||||
"564": "Unknown command. The debugChannel was not wired up properly.",
|
||||
"565": "resolveDebugMessage/closeDebugChannel should not be called for a Request that wasn't kept alive. This is a bug in React.",
|
||||
"566": "FragmentInstance.experimental_scrollIntoView() does not support scrollIntoViewOptions. Use the alignToTop boolean instead."
|
||||
"566": "FragmentInstance.experimental_scrollIntoView() does not support scrollIntoViewOptions. Use the alignToTop boolean instead.",
|
||||
"567": "Already initialized stream.",
|
||||
"568": "Already initialized typed array.",
|
||||
"569": "Cannot have cyclic thenables.",
|
||||
"570": "Invalid reference."
|
||||
}
|
||||
|
||||
@@ -124,25 +124,34 @@ async function main() {
|
||||
throw new Error(`Unknown release channel ${argv.releaseChannel}`);
|
||||
}
|
||||
} else {
|
||||
// Running locally, no concurrency. Move each channel's build artifacts into
|
||||
// a temporary directory so that they don't conflict.
|
||||
buildForChannel('stable', '', '');
|
||||
const stableDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', stableDir);
|
||||
processStable(stableDir);
|
||||
buildForChannel('experimental', '', '');
|
||||
const experimentalDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', experimentalDir);
|
||||
processExperimental(experimentalDir);
|
||||
const releaseChannel = argv.releaseChannel;
|
||||
if (releaseChannel === 'stable') {
|
||||
buildForChannel('stable', '', '');
|
||||
processStable('./build');
|
||||
} else if (releaseChannel === 'experimental') {
|
||||
buildForChannel('experimental', '', '');
|
||||
processExperimental('./build');
|
||||
} else {
|
||||
// Running locally, no concurrency. Move each channel's build artifacts into
|
||||
// a temporary directory so that they don't conflict.
|
||||
buildForChannel('stable', '', '');
|
||||
const stableDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', stableDir);
|
||||
processStable(stableDir);
|
||||
buildForChannel('experimental', '', '');
|
||||
const experimentalDir = tmp.dirSync().name;
|
||||
crossDeviceRenameSync('./build', experimentalDir);
|
||||
processExperimental(experimentalDir);
|
||||
|
||||
// Then merge the experimental folder into the stable one. processExperimental
|
||||
// will have already removed conflicting files.
|
||||
//
|
||||
// In CI, merging is handled by the GitHub Download Artifacts plugin.
|
||||
mergeDirsSync(experimentalDir + '/', stableDir + '/');
|
||||
// Then merge the experimental folder into the stable one. processExperimental
|
||||
// will have already removed conflicting files.
|
||||
//
|
||||
// In CI, merging is handled by the GitHub Download Artifacts plugin.
|
||||
mergeDirsSync(experimentalDir + '/', stableDir + '/');
|
||||
|
||||
// Now restore the combined directory back to its original name
|
||||
crossDeviceRenameSync(stableDir, './build');
|
||||
// Now restore the combined directory back to its original name
|
||||
crossDeviceRenameSync(stableDir, './build');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -476,25 +476,6 @@ const bundles = [
|
||||
'util',
|
||||
],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry:
|
||||
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
|
||||
name: 'react-server-dom-webpack-server.node.unbundled',
|
||||
condition: 'react-server',
|
||||
global: 'ReactServerDOMServer',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: [
|
||||
'react',
|
||||
'react-dom',
|
||||
'async_hooks',
|
||||
'crypto',
|
||||
'stream',
|
||||
'util',
|
||||
],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
@@ -529,17 +510,6 @@ const bundles = [
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'react-dom', 'util', 'crypto'],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry:
|
||||
'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
|
||||
name: 'react-server-dom-webpack-client.node.unbundled',
|
||||
global: 'ReactServerDOMClient',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'react-dom', 'util', 'crypto'],
|
||||
},
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
@@ -786,6 +756,63 @@ const bundles = [
|
||||
externals: ['acorn'],
|
||||
},
|
||||
|
||||
/******* React Server DOM Unbundled Server *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
|
||||
name: 'react-server-dom-unbundled-server.node',
|
||||
condition: 'react-server',
|
||||
global: 'ReactServerDOMServer',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: [
|
||||
'react',
|
||||
'react-dom',
|
||||
'async_hooks',
|
||||
'crypto',
|
||||
'stream',
|
||||
'util',
|
||||
],
|
||||
},
|
||||
|
||||
/******* React Server DOM Unbundled Client *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
|
||||
name: 'react-server-dom-unbundled-client.node',
|
||||
global: 'ReactServerDOMClient',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'react-dom', 'util', 'crypto'],
|
||||
},
|
||||
|
||||
/******* React Server DOM Unbundled Node.js Loader *******/
|
||||
{
|
||||
bundleTypes: [ESM_PROD],
|
||||
moduleType: RENDERER_UTILS,
|
||||
entry: 'react-server-dom-unbundled/node-loader',
|
||||
condition: 'react-server',
|
||||
global: 'ReactServerUnbundledNodeLoader',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['acorn'],
|
||||
},
|
||||
|
||||
/******* React Server DOM Unbundled Node.js CommonJS Loader *******/
|
||||
{
|
||||
bundleTypes: [NODE_ES2015],
|
||||
moduleType: RENDERER_UTILS,
|
||||
entry: 'react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister',
|
||||
name: 'react-server-dom-unbundled-node-register',
|
||||
condition: 'react-server',
|
||||
global: 'ReactFlightUnbundledNodeRegister',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['url', 'module', 'react-server-dom-unbundled/server'],
|
||||
},
|
||||
|
||||
/******* React Suspense Test Utils *******/
|
||||
{
|
||||
bundleTypes: [NODE_ES2015],
|
||||
|
||||
@@ -63,8 +63,8 @@ module.exports = [
|
||||
'react-dom/src/server/react-dom-server.node.js',
|
||||
'react-dom/test-utils',
|
||||
'react-dom/unstable_server-external-runtime',
|
||||
'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
|
||||
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
|
||||
'react-server-dom-webpack/src/client/react-flight-dom-client.node',
|
||||
'react-server-dom-webpack/src/server/react-flight-dom-server.node',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
@@ -84,20 +84,13 @@ module.exports = [
|
||||
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
|
||||
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
|
||||
'react-server-dom-webpack',
|
||||
'react-server-dom-webpack/client.node.unbundled',
|
||||
'react-server-dom-webpack/server',
|
||||
'react-server-dom-webpack/server.node.unbundled',
|
||||
'react-server-dom-webpack/static',
|
||||
'react-server-dom-webpack/static.node.unbundled',
|
||||
'react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-webpack/client.node
|
||||
'react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js',
|
||||
'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
|
||||
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js',
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer.js',
|
||||
'react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js', // react-server-dom-webpack/src/server/react-flight-dom-server.node
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
'react-server/src/ReactFlightServerConfigDebugNode.js',
|
||||
@@ -240,6 +233,49 @@ module.exports = [
|
||||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-node-unbundled',
|
||||
entryPoints: [
|
||||
'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
|
||||
'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom-bindings',
|
||||
'react-dom/client',
|
||||
'react-dom/profiling',
|
||||
'react-dom/server',
|
||||
'react-dom/server.node',
|
||||
'react-dom/static',
|
||||
'react-dom/static.node',
|
||||
'react-dom/src/server/react-dom-server.node',
|
||||
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
|
||||
'react-dom/src/server/ReactDOMFizzStaticNode.js',
|
||||
'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
|
||||
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
|
||||
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
|
||||
'react-server-dom-unbundled',
|
||||
'react-server-dom-unbundled/client',
|
||||
'react-server-dom-unbundled/server',
|
||||
'react-server-dom-unbundled/server.node',
|
||||
'react-server-dom-unbundled/static',
|
||||
'react-server-dom-unbundled/static.node',
|
||||
'react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-unbundled/client.node
|
||||
'react-server-dom-unbundled/src/client/ReactFlightDOMClientNode.js', // react-server-dom-unbundled/client.node
|
||||
'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js',
|
||||
'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
|
||||
'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
|
||||
'react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js', // react-server-dom-unbundled/src/server/react-flight-dom-server.node
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-shell',
|
||||
'react-devtools-shared',
|
||||
'shared/ReactDOMSharedInternals',
|
||||
'react-server/src/ReactFlightServerConfigDebugNode.js',
|
||||
],
|
||||
isFlowTyped: true,
|
||||
isServerSupported: true,
|
||||
},
|
||||
{
|
||||
shortName: 'dom-bun',
|
||||
entryPoints: ['react-dom/src/server/react-dom-server.bun.js'],
|
||||
|
||||
Reference in New Issue
Block a user