[Flight][Float] Preinitialize module imports during SSR (#27314)
Currently when we SSR a Flight response we do not emit any resources for module imports. This means that when the client hydrates it won't have already loaded the necessary scripts to satisfy the Imports defined in the Flight payload which will lead to a delay in hydration completing. This change updates `react-server-dom-webpack` and `react-server-dom-esm` to emit async script tags in the head when we encounter a modules in the flight response. To support this we need some additional server configuration. We need to know the path prefix for chunk loading and whether the chunks will load with CORS or not (and if so with what configuration).
Josh Story committed
Sep 27, 2023 at 09:53 UTC
701ac2e57290f913502c6012c15e815f412f84b7
48 files changed
+1021
-288
fixtures/flight-esm/.nvmrc
new
+1
@@ -0,0 +1 @@
1
+v18
fixtures/flight-esm/server/global.js
+28
-10
@@ -10,6 +10,7 @@ const compress = require('compression');
10
const chalk = require('chalk');
11
const express = require('express');
12
const http = require('http');
13
+const React = require('react');
14
15
const {renderToPipeableStream} = require('react-dom/server');
16
const {createFromNodeStream} = require('react-server-dom-esm/client');
@@ -62,23 +63,39 @@ app.all('/', async function (req, res, next) {
63
if (req.accepts('text/html')) {
64
try {
65
const rscResponse = await promiseForData;
65
-
66
const moduleBaseURL = '/src';
67
68
// For HTML, we're a "client" emulator that runs the client code,
69
// so we start by consuming the RSC payload. This needs the local file path
70
// to load the source files from as well as the URL path for preloads.
71
- const root = await createFromNodeStream(
72
- rscResponse,
73
- moduleBasePath,
74
- moduleBaseURL
75
- );
71
+
72
+ let root;
73
+ let Root = () => {
74
+ if (root) {
75
+ return React.use(root);
76
+ }
77
+
78
+ return React.use(
79
+ (root = createFromNodeStream(
80
+ rscResponse,
81
+ moduleBasePath,
82
+ moduleBaseURL
83
+ ))
84
+ );
85
+ };
86
// Render it into HTML by resolving the client components
87
res.set('Content-type', 'text/html');
78
- const {pipe} = renderToPipeableStream(root, {
79
- // TODO: bootstrapModules inserts a preload before the importmap which causes
80
- // the import map to be invalid. We need to fix that in Float somehow.
81
- // bootstrapModules: ['/src/index.js'],
88
+ const {pipe} = renderToPipeableStream(React.createElement(Root), {
89
+ importMap: {
90
+ imports: {
91
+ react: 'https://esm.sh/react@experimental?pin=v124&dev',
92
+ 'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
93
+ 'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
94
+ 'react-server-dom-esm/client':
95
+ '/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
96
+ },
97
+ },
98
+ bootstrapModules: ['/src/index.js'],
99
});
100
pipe(res);
101
} catch (e) {
@@ -89,6 +106,7 @@ app.all('/', async function (req, res, next) {
106
} else {
107
try {
108
const rscResponse = await promiseForData;
109
+
110
// For other request, we pass-through the RSC payload.
111
res.set('Content-type', 'text/x-component');
112
rscResponse.on('data', data => {
fixtures/flight-esm/src/App.js
+1
-19
@@ -9,16 +9,6 @@ import {getServerState} from './ServerState.js';
9
10
const h = React.createElement;
11
12
-const importMap = {
13
- imports: {
14
- react: 'https://esm.sh/react@experimental?pin=v124&dev',
15
- 'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
16
- 'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
17
- 'react-server-dom-esm/client':
18
- '/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
19
- },
20
-};
21
-
12
export default async function App() {
13
const res = await fetch('http://localhost:3001/todos');
14
const todos = await res.json();
@@ -42,12 +32,6 @@ export default async function App() {
32
rel: 'stylesheet',
33
href: '/src/style.css',
34
precedence: 'default',
45
- }),
46
- h('script', {
47
- type: 'importmap',
48
- dangerouslySetInnerHTML: {
49
- __html: JSON.stringify(importMap),
50
- },
35
})
36
),
37
h(
@@ -84,9 +68,7 @@ export default async function App() {
68
'Like'
69
)
70
)
87
- ),
88
- // TODO: Move this to bootstrapModules.
89
- h('script', {type: 'module', src: '/src/index.js'})
71
+ )
72
)
73
);
74
}
fixtures/flight-esm/yarn.lock
+11
-11
@@ -540,17 +540,17 @@ raw-body@2.5.2:
540
unpipe "1.0.0"
541
542
react-dom@experimental:
543
- version "0.0.0-experimental-018c58c9c-20230601"
544
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-018c58c9c-20230601.tgz#2cc0ac824b83bab2ac1c6187f241dbd5dcd5201b"
545
- integrity sha512-hwRsyoG1R3Tub0nUa72YvNcqPvU+pTcr9dadOnUCKKfSiYVbBCy7LxmkqLauCD8OjNJMlwtMgG4UAgtidclYGQ==
543
+ version "0.0.0-experimental-b9be4537c-20230905"
544
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-b9be4537c-20230905.tgz#b078d6d06041e0c98ce5a2f5e9ff26a2e308eb41"
545
+ integrity sha512-veAFNVj81lUYhYlucYm3kbj2BhakG57XYkWC/QHVEZDk4Hm2qxM9RUk7gn8dWs9Eq7KR6Q+JWiSH3ZbObQTV9g==
546
dependencies:
547
loose-envify "^1.1.0"
548
- scheduler "0.0.0-experimental-018c58c9c-20230601"
548
+ scheduler "0.0.0-experimental-b9be4537c-20230905"
549
550
react@experimental:
551
- version "0.0.0-experimental-018c58c9c-20230601"
552
- resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-018c58c9c-20230601.tgz#ab04d1243c8f83b0166ed342056fa6b38ab2cd23"
553
- integrity sha512-nSQIBsZ26Ii899pZ9cRt/6uQLbIUEAcDIivvAQyaHp4pWm289aB+7AK7VCWojAJIf4OStCuWs2berZsk4mzLVg==
551
+ version "0.0.0-experimental-b9be4537c-20230905"
552
+ resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-b9be4537c-20230905.tgz#3c2352b42b8024544a12dcd96f2700313cebcb6b"
553
+ integrity sha512-QNeK74S7AU94j4vCxet2S76HqxpF6CJo1pG3XcgY2NravyXdWYszrRDNHrfu86gGNwAQvSU+YpStYn/i0b9tLA==
554
dependencies:
555
loose-envify "^1.1.0"
556
@@ -588,10 +588,10 @@ safe-buffer@5.1.2:
588
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
589
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
590
591
-scheduler@0.0.0-experimental-018c58c9c-20230601:
592
- version "0.0.0-experimental-018c58c9c-20230601"
593
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-018c58c9c-20230601.tgz#4f083614f8e857bab63dd90b4b37b03783dafe6b"
594
- integrity sha512-otUM7AAAnCoJ5/0jTQwUQ7NhxjgcPEdrfzW7NfkpocrDoTUbql1kIGIhj9L9POMVFDI/wcZzRNK/oIEWsB4DPw==
591
+scheduler@0.0.0-experimental-b9be4537c-20230905:
592
+ version "0.0.0-experimental-b9be4537c-20230905"
593
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-b9be4537c-20230905.tgz#f0fe5a710ce15a9d637c28e9f019a4100e1f3f34"
594
+ integrity sha512-V5P9LOS+c5CG7qaCJu+Qgcz9eh/dP4nBszj3w1MCgZnMtAna6+J8ZuuUnRDMeY86F8KH+cY8Q5beIvAL2noMzA==
595
dependencies:
596
loose-envify "^1.1.0"
597
fixtures/flight/.nvmrc
new
+1
@@ -0,0 +1 @@
1
+v18
fixtures/flight/server/global.js
+34
-7
@@ -33,6 +33,7 @@ const compress = require('compression');
33
const chalk = require('chalk');
34
const express = require('express');
35
const http = require('http');
36
+const React = require('react');
37
38
const {renderToPipeableStream} = require('react-dom/server');
39
const {createFromNodeStream} = require('react-server-dom-webpack/client');
@@ -62,6 +63,11 @@ if (process.env.NODE_ENV === 'development') {
63
webpackMiddleware(compiler, {
64
publicPath: paths.publicUrlOrPath.slice(0, -1),
65
serverSideRender: true,
66
+ headers: () => {
67
+ return {
68
+ 'Cache-Control': 'no-store, must-revalidate',
69
+ };
70
+ },
71
})
72
);
73
app.use(webpackHotMiddleware(compiler));
@@ -121,12 +127,13 @@ app.all('/', async function (req, res, next) {
127
buildPath = path.join(__dirname, '../build/');
128
}
129
// Read the module map from the virtual file system.
124
- const moduleMap = JSON.parse(
130
+ const ssrManifest = JSON.parse(
131
await virtualFs.readFile(
132
path.join(buildPath, 'react-ssr-manifest.json'),
133
'utf8'
134
)
135
);
136
+
137
// Read the entrypoints containing the initial JS to bootstrap everything.
138
// For other pages, the chunks in the RSC payload are enough.
139
const mainJSChunks = JSON.parse(
@@ -138,15 +145,35 @@ app.all('/', async function (req, res, next) {
145
// For HTML, we're a "client" emulator that runs the client code,
146
// so we start by consuming the RSC payload. This needs a module
147
// map that reverse engineers the client-side path to the SSR path.
141
- const {root, formState} = await createFromNodeStream(
142
- rscResponse,
143
- moduleMap
144
- );
148
+
149
+ // This is a bad hack to set the form state after SSR has started. It works
150
+ // because we block the root component until we have the form state and
151
+ // any form that reads it necessarily will come later. It also only works
152
+ // because the formstate type is an object which may change in the future
153
+ const lazyFormState = [];
154
+
155
+ let cachedResult = null;
156
+ async function getRootAndFormState() {
157
+ const {root, formState} = await createFromNodeStream(
158
+ rscResponse,
159
+ ssrManifest
160
+ );
161
+ // We shouldn't be assuming formState is an object type but at the moment
162
+ // we have no way of setting the form state from within the render
163
+ Object.assign(lazyFormState, formState);
164
+ return root;
165
+ }
166
+ let Root = () => {
167
+ if (!cachedResult) {
168
+ cachedResult = getRootAndFormState();
169
+ }
170
+ return React.use(cachedResult);
171
+ };
172
// Render it into HTML by resolving the client components
173
res.set('Content-type', 'text/html');
147
- const {pipe} = renderToPipeableStream(root, {
174
+ const {pipe} = renderToPipeableStream(React.createElement(Root), {
175
bootstrapScripts: mainJSChunks,
149
- experimental_formState: formState,
176
+ experimental_formState: lazyFormState,
177
});
178
pipe(res);
179
} catch (e) {
packages/react-client/src/ReactFlightClient.js
+17
-3
@@ -13,8 +13,9 @@ import type {LazyComponent} from 'react/src/ReactLazy';
13
import type {
14
ClientReference,
15
ClientReferenceMetadata,
16
- SSRManifest,
16
+ SSRModuleMap,
17
StringDecoder,
18
+ ModuleLoading,
19
} from './ReactFlightClientConfig';
20
21
import type {
@@ -36,6 +37,7 @@ import {
37
readPartialStringChunk,
38
readFinalStringChunk,
39
createStringDecoder,
40
+ prepareDestinationForModule,
41
} from './ReactFlightClientConfig';
42
43
import {registerServerReference} from './ReactFlightReplyClient';
@@ -178,8 +180,10 @@ Chunk.prototype.then = function <T>(
180
};
181
182
export type Response = {
181
- _bundlerConfig: SSRManifest,
183
+ _bundlerConfig: SSRModuleMap,
184
+ _moduleLoading: ModuleLoading,
185
_callServer: CallServerCallback,
186
+ _nonce: ?string,
187
_chunks: Map<number, SomeChunk<any>>,
188
_fromJSON: (key: string, value: JSONValue) => any,
189
_stringDecoder: StringDecoder,
@@ -706,13 +710,17 @@ function missingCall() {
710
}
711
712
export function createResponse(
709
- bundlerConfig: SSRManifest,
713
+ bundlerConfig: SSRModuleMap,
714
+ moduleLoading: ModuleLoading,
715
callServer: void | CallServerCallback,
716
+ nonce: void | string,
717
): Response {
718
const chunks: Map<number, SomeChunk<any>> = new Map();
719
const response: Response = {
720
_bundlerConfig: bundlerConfig,
721
+ _moduleLoading: moduleLoading,
722
_callServer: callServer !== undefined ? callServer : missingCall,
723
+ _nonce: nonce,
724
_chunks: chunks,
725
_stringDecoder: createStringDecoder(),
726
_fromJSON: (null: any),
@@ -774,6 +782,12 @@ function resolveModule(
782
clientReferenceMetadata,
783
);
784
785
+ prepareDestinationForModule(
786
+ response._moduleLoading,
787
+ response._nonce,
788
+ clientReferenceMetadata,
789
+ );
790
+
791
// TODO: Add an option to encode modules that are lazy loaded.
792
// For now we preload all modules as early as possible since it's likely
793
// that we'll need them.
packages/react-client/src/forks/ReactFlightClientConfig.custom.js
+4
-1
@@ -25,7 +25,8 @@
25
26
declare var $$$config: any;
27
28
-export opaque type SSRManifest = mixed;
28
+export opaque type ModuleLoading = mixed;
29
+export opaque type SSRModuleMap = mixed;
30
export opaque type ServerManifest = mixed;
31
export opaque type ServerReferenceId = string;
32
export opaque type ClientReferenceMetadata = mixed;
@@ -35,6 +36,8 @@ export const resolveServerReference = $$$config.resolveServerReference;
36
export const preloadModule = $$$config.preloadModule;
37
export const requireModule = $$$config.requireModule;
38
export const dispatchHint = $$$config.dispatchHint;
39
+export const prepareDestinationForModule =
40
+ $$$config.prepareDestinationForModule;
41
export const usedWithSSR = true;
42
43
export opaque type Source = mixed;
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js
+2
-1
@@ -8,6 +8,7 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientConfigBrowser';
11
-export * from 'react-server-dom-esm/src/ReactFlightClientConfigESMBundler';
11
+export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM';
12
+export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMBrowser';
13
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
14
export const usedWithSSR = false;
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js
+3
-1
@@ -8,6 +8,8 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientConfigBrowser';
11
-export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
11
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
12
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser';
13
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackBrowser';
14
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
15
export const usedWithSSR = false;
packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js
+3
-1
@@ -11,7 +11,8 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
11
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
12
13
export type Response = any;
14
-export opaque type SSRManifest = mixed;
14
+export opaque type ModuleLoading = mixed;
15
+export opaque type SSRModuleMap = mixed;
16
export opaque type ServerManifest = mixed;
17
export opaque type ServerReferenceId = string;
18
export opaque type ClientReferenceMetadata = mixed;
@@ -20,4 +21,5 @@ export const resolveClientReference: any = null;
21
export const resolveServerReference: any = null;
22
export const preloadModule: any = null;
23
export const requireModule: any = null;
24
+export const prepareDestinationForModule: any = null;
25
export const usedWithSSR = true;
packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js
+3
-1
@@ -8,6 +8,8 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientConfigBrowser';
11
-export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
11
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
12
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer';
13
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
14
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
15
export const usedWithSSR = true;
packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js
+3
-1
@@ -11,7 +11,8 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
11
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
12
13
export type Response = any;
14
-export opaque type SSRManifest = mixed;
14
+export opaque type ModuleLoading = mixed;
15
+export opaque type SSRModuleMap = mixed;
16
export opaque type ServerManifest = mixed;
17
export opaque type ServerReferenceId = string;
18
export opaque type ClientReferenceMetadata = mixed;
@@ -20,4 +21,5 @@ export const resolveClientReference: any = null;
21
export const resolveServerReference: any = null;
22
export const preloadModule: any = null;
23
export const requireModule: any = null;
24
+export const prepareDestinationForModule: any = null;
25
export const usedWithSSR = true;
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js
+3
-2
@@ -7,7 +7,8 @@
7
* @flow
8
*/
9
10
-export * from 'react-client/src/ReactFlightClientConfigBrowser';
11
-export * from 'react-server-dom-esm/src/ReactFlightClientConfigESMBundler';
10
+export * from 'react-client/src/ReactFlightClientConfigNode';
11
+export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM';
12
+export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMServer';
13
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
14
export const usedWithSSR = true;
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js
+3
-1
@@ -8,6 +8,8 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientConfigNode';
11
-export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
11
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
12
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer';
13
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
14
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
15
export const usedWithSSR = true;
packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js
+2
-1
@@ -8,6 +8,7 @@
8
*/
9
10
export * from 'react-client/src/ReactFlightClientConfigNode';
11
-export * from 'react-server-dom-webpack/src/ReactFlightClientConfigNodeBundler';
11
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerNode';
12
+export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
13
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
14
export const usedWithSSR = true;
packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js
+30
@@ -15,6 +15,8 @@ import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM';
15
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
16
const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
17
18
+import {getCrossOriginString} from './crossOriginStrings';
19
+
20
export function dispatchHint<Code: HintCode>(
21
code: Code,
22
model: HintModel<Code>,
@@ -110,3 +112,31 @@ export function dispatchHint<Code: HintCode>(
112
function refineModel<T>(code: T, model: HintModel<any>): HintModel<T> {
113
return model;
114
}
115
+
116
+export function preinitModuleForSSR(
117
+ href: string,
118
+ nonce: ?string,
119
+ crossOrigin: ?string,
120
+) {
121
+ const dispatcher = ReactDOMCurrentDispatcher.current;
122
+ if (dispatcher) {
123
+ dispatcher.preinitModuleScript(href, {
124
+ crossOrigin: getCrossOriginString(crossOrigin),
125
+ nonce,
126
+ });
127
+ }
128
+}
129
+
130
+export function preinitScriptForSSR(
131
+ href: string,
132
+ nonce: ?string,
133
+ crossOrigin: ?string,
134
+) {
135
+ const dispatcher = ReactDOMCurrentDispatcher.current;
136
+ if (dispatcher) {
137
+ dispatcher.preinitScript(href, {
138
+ crossOrigin: getCrossOriginString(crossOrigin),
139
+ nonce,
140
+ });
141
+ }
142
+}
packages/react-dom-bindings/src/shared/crossOriginStrings.js
new
+30
@@ -0,0 +1,30 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export opaque type CrossOriginString: string = string;
11
+
12
+export function getCrossOriginString(input: ?string): ?CrossOriginString {
13
+ if (typeof input === 'string') {
14
+ return input === 'use-credentials' ? input : '';
15
+ }
16
+ return undefined;
17
+}
18
+
19
+export function getCrossOriginStringAs(
20
+ as: ?string,
21
+ input: ?string,
22
+): ?CrossOriginString {
23
+ if (as === 'font') {
24
+ return '';
25
+ }
26
+ if (typeof input === 'string') {
27
+ return input === 'use-credentials' ? input : '';
28
+ }
29
+ return undefined;
30
+}
packages/react-dom/src/shared/ReactDOMFloat.js
+29
-30
@@ -7,7 +7,6 @@
7
* @flow
8
*/
9
import type {
10
- CrossOriginEnum,
10
PreconnectOptions,
11
PreloadOptions,
12
PreloadModuleOptions,
@@ -18,6 +17,11 @@ import type {
17
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
18
const Dispatcher = ReactDOMSharedInternals.Dispatcher;
19
20
+import {
21
+ getCrossOriginString,
22
+ getCrossOriginStringAs,
23
+} from 'react-dom-bindings/src/shared/crossOriginStrings';
24
+
25
export function prefetchDNS(href: string) {
26
if (__DEV__) {
27
if (typeof href !== 'string' || !href) {
@@ -74,7 +78,7 @@ export function preconnect(href: string, options?: ?PreconnectOptions) {
78
const dispatcher = Dispatcher.current;
79
if (dispatcher && typeof href === 'string') {
80
const crossOrigin = options
77
- ? getCrossOrigin('preconnect', options.crossOrigin)
81
+ ? getCrossOriginString(options.crossOrigin)
82
: null;
83
dispatcher.preconnect(href, crossOrigin);
84
}
@@ -117,7 +121,7 @@ export function preload(href: string, options: PreloadOptions) {
121
typeof options.as === 'string'
122
) {
123
const as = options.as;
120
- const crossOrigin = getCrossOrigin(as, options.crossOrigin);
124
+ const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
125
dispatcher.preload(href, as, {
126
crossOrigin,
127
integrity:
@@ -172,7 +176,10 @@ export function preloadModule(href: string, options?: ?PreloadModuleOptions) {
176
const dispatcher = Dispatcher.current;
177
if (dispatcher && typeof href === 'string') {
178
if (options) {
175
- const crossOrigin = getCrossOrigin(options.as, options.crossOrigin);
179
+ const crossOrigin = getCrossOriginStringAs(
180
+ options.as,
181
+ options.crossOrigin,
182
+ );
183
dispatcher.preloadModule(href, {
184
as:
185
typeof options.as === 'string' && options.as !== 'script'
@@ -218,7 +225,7 @@ export function preinit(href: string, options: PreinitOptions) {
225
typeof options.as === 'string'
226
) {
227
const as = options.as;
221
- const crossOrigin = getCrossOrigin(as, options.crossOrigin);
228
+ const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
229
const integrity =
230
typeof options.integrity === 'string' ? options.integrity : undefined;
231
const fetchPriority =
@@ -296,21 +303,23 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) {
303
}
304
const dispatcher = Dispatcher.current;
305
if (dispatcher && typeof href === 'string') {
299
- if (
300
- options == null ||
301
- (typeof options === 'object' &&
302
- (options.as == null || options.as === 'script'))
303
- ) {
304
- const crossOrigin = options
305
- ? getCrossOrigin(undefined, options.crossOrigin)
306
- : undefined;
307
- dispatcher.preinitModuleScript(href, {
308
- crossOrigin,
309
- integrity:
310
- options && typeof options.integrity === 'string'
311
- ? options.integrity
312
- : undefined,
313
- });
306
+ if (typeof options === 'object' && options !== null) {
307
+ if (options.as == null || options.as === 'script') {
308
+ const crossOrigin = getCrossOriginStringAs(
309
+ options.as,
310
+ options.crossOrigin,
311
+ );
312
+ dispatcher.preinitModuleScript(href, {
313
+ crossOrigin,
314
+ integrity:
315
+ typeof options.integrity === 'string'
316
+ ? options.integrity
317
+ : undefined,
318
+ nonce: typeof options.nonce === 'string' ? options.nonce : undefined,
319
+ });
320
+ }
321
+ } else if (options == null) {
322
+ dispatcher.preinitModuleScript(href);
323
}
324
}
325
// We don't error because preinit needs to be resilient to being called in a variety of scopes
@@ -318,16 +327,6 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) {
327
// so we favor silent bailout over warning or erroring.
328
}
329
321
-function getCrossOrigin(as: ?string, crossOrigin: ?string): ?CrossOriginEnum {
322
- return as === 'font'
323
- ? ''
324
- : typeof crossOrigin === 'string'
325
- ? crossOrigin === 'use-credentials'
326
- ? 'use-credentials'
327
- : ''
328
- : undefined;
329
-}
330
-
330
function getValueDescriptorExpectingObjectForWarning(thing: any): string {
331
return thing === null
332
? '`null`'
packages/react-dom/src/shared/ReactDOMTypes.js
+5
-3
@@ -7,6 +7,8 @@
7
* @flow
8
*/
9
10
+import type {CrossOriginString} from 'react-dom-bindings/src/shared/crossOriginStrings';
11
+
12
export type PrefetchDNSOptions = {};
13
export type PreconnectOptions = {crossOrigin?: string};
14
export type PreloadOptions = {
@@ -41,7 +43,7 @@ export type PreinitModuleOptions = {
43
nonce?: string,
44
};
45
44
-export type CrossOriginEnum = '' | 'use-credentials';
46
+export type CrossOriginEnum = '' | 'use-credentials' | CrossOriginString;
47
export type FetchPriorityEnum = 'high' | 'low' | 'auto';
48
49
export type PreloadImplOptions = {
@@ -61,12 +63,12 @@ export type PreloadModuleImplOptions = {
63
nonce?: ?string,
64
};
65
export type PreinitStyleOptions = {
64
- crossOrigin?: ?string,
66
+ crossOrigin?: ?CrossOriginEnum,
67
integrity?: ?string,
68
fetchPriority?: ?FetchPriorityEnum,
69
};
70
export type PreinitScriptOptions = {
69
- crossOrigin?: ?string,
71
+ crossOrigin?: ?CrossOriginEnum,
72
integrity?: ?string,
73
fetchPriority?: ?FetchPriorityEnum,
74
nonce?: ?string,
packages/react-noop-renderer/src/ReactNoopFlightClient.js
+1
@@ -35,6 +35,7 @@ const {createResponse, processBinaryChunk, getRoot, close} = ReactFlightClient({
35
resolveClientReference(bundlerConfig: null, idx: string) {
36
return idx;
37
},
38
+ prepareDestinationForModule(moduleLoading: null, metadata: string) {},
39
preloadModule(idx: string) {},
40
requireModule(idx: string) {
41
return readModule(idx);
packages/react-server-dom-esm/src/ReactFlightClientConfigBundlerESM.js
renamed
+19
-2
@@ -12,13 +12,16 @@ import type {
12
FulfilledThenable,
13
RejectedThenable,
14
} from 'shared/ReactTypes';
15
+import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
16
16
-export type SSRManifest = string; // Module root path
17
+export type SSRModuleMap = string; // Module root path
18
19
export type ServerManifest = string; // Module root path
20
21
export type ServerReferenceId = string;
22
23
+import {prepareDestinationForModuleImpl} from 'react-client/src/ReactFlightClientConfig';
24
+
25
export opaque type ClientReferenceMetadata = [
26
string, // module path
27
string, // export name
@@ -30,8 +33,22 @@ export opaque type ClientReference<T> = {
33
name: string,
34
};
35
36
+// The reason this function needs to defined here in this file instead of just
37
+// being exported directly from the WebpackDestination... file is because the
38
+// ClientReferenceMetadata is opaque and we can't unwrap it there.
39
+// This should get inlined and we could also just implement an unwrapping function
40
+// though that risks it getting used in places it shouldn't be. This is unfortunate
41
+// but currently it seems to be the best option we have.
42
+export function prepareDestinationForModule(
43
+ moduleLoading: ModuleLoading,
44
+ nonce: ?string,
45
+ metadata: ClientReferenceMetadata,
46
+) {
47
+ prepareDestinationForModuleImpl(moduleLoading, metadata[0], nonce);
48
+}
49
+
50
export function resolveClientReference<T>(
34
- bundlerConfig: SSRManifest,
51
+ bundlerConfig: SSRModuleMap,
52
metadata: ClientReferenceMetadata,
53
): ClientReference<T> {
54
const baseURL = bundlerConfig;
packages/react-server-dom-esm/src/ReactFlightClientConfigTargetESMBrowser.js
new
+18
@@ -0,0 +1,18 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export type ModuleLoading = null;
11
+
12
+export function prepareDestinationForModuleImpl(
13
+ moduleLoading: ModuleLoading,
14
+ chunks: mixed,
15
+ nonce: ?string,
16
+) {
17
+ // In the browser we don't need to prepare our destination since the browser is the Destination
18
+}
packages/react-server-dom-esm/src/ReactFlightClientConfigTargetESMServer.js
new
+35
@@ -0,0 +1,35 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import {preinitModuleForSSR} from 'react-client/src/ReactFlightClientConfig';
11
+
12
+export type ModuleLoading =
13
+ | null
14
+ | string
15
+ | {
16
+ prefix: string,
17
+ crossOrigin?: string,
18
+ };
19
+
20
+export function prepareDestinationForModuleImpl(
21
+ moduleLoading: ModuleLoading,
22
+ // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
23
+ mod: string,
24
+ nonce: ?string,
25
+) {
26
+ if (typeof moduleLoading === 'string') {
27
+ preinitModuleForSSR(moduleLoading + mod, nonce, undefined);
28
+ } else if (moduleLoading !== null) {
29
+ preinitModuleForSSR(
30
+ moduleLoading.prefix + mod,
31
+ nonce,
32
+ moduleLoading.crossOrigin,
33
+ );
34
+ }
35
+}
packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js
+2
@@ -36,7 +36,9 @@ export type Options = {
36
function createResponseFromOptions(options: void | Options) {
37
return createResponse(
38
options && options.moduleBaseURL ? options.moduleBaseURL : '',
39
+ null,
40
options && options.callServer ? options.callServer : undefined,
41
+ undefined, // nonce
42
);
43
}
44
packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js
+12
-2
@@ -38,12 +38,22 @@ export function createServerReference<A: Iterable<any>, T>(
38
return createServerReferenceImpl(id, noServerCall);
39
}
40
41
+export type Options = {
42
+ nonce?: string,
43
+};
44
+
45
function createFromNodeStream<T>(
46
stream: Readable,
47
moduleRootPath: string,
44
- moduleBaseURL: string, // TODO: Used for preloading hints
48
+ moduleBaseURL: string,
49
+ options?: Options,
50
): Thenable<T> {
46
- const response: Response = createResponse(moduleRootPath, noServerCall);
51
+ const response: Response = createResponse(
52
+ moduleRootPath,
53
+ moduleBaseURL,
54
+ noServerCall,
55
+ options && typeof options.nonce === 'string' ? options.nonce : undefined,
56
+ );
57
stream.on('data', chunk => {
58
processBinaryChunk(response, chunk);
59
});
packages/react-server-dom-webpack/src/ReactFlightClientConfigBundlerNode.js
renamed
+33
-13
@@ -13,7 +13,18 @@ import type {
13
RejectedThenable,
14
} from 'shared/ReactTypes';
15
16
-export type SSRManifest = {
16
+import type {ImportMetadata} from './shared/ReactFlightImportMetadata';
17
+import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
18
+
19
+import {
20
+ ID,
21
+ CHUNKS,
22
+ NAME,
23
+ isAsyncImport,
24
+} from './shared/ReactFlightImportMetadata';
25
+import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
26
+
27
+export type SSRModuleMap = {
28
[clientId: string]: {
29
[clientExportName: string]: ClientReference<any>,
30
},
@@ -23,12 +34,7 @@ export type ServerManifest = void;
34
35
export type ServerReferenceId = string;
36
26
-export opaque type ClientReferenceMetadata = {
27
- id: string,
28
- chunks: Array<string>,
29
- name: string,
30
- async?: boolean,
31
-};
37
+export opaque type ClientReferenceMetadata = ImportMetadata;
38
39
// eslint-disable-next-line no-unused-vars
40
export opaque type ClientReference<T> = {
@@ -37,12 +43,26 @@ export opaque type ClientReference<T> = {
43
async?: boolean,
44
};
45
46
+// The reason this function needs to defined here in this file instead of just
47
+// being exported directly from the WebpackDestination... file is because the
48
+// ClientReferenceMetadata is opaque and we can't unwrap it there.
49
+// This should get inlined and we could also just implement an unwrapping function
50
+// though that risks it getting used in places it shouldn't be. This is unfortunate
51
+// but currently it seems to be the best option we have.
52
+export function prepareDestinationForModule(
53
+ moduleLoading: ModuleLoading,
54
+ nonce: ?string,
55
+ metadata: ClientReferenceMetadata,
56
+) {
57
+ prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce);
58
+}
59
+
60
export function resolveClientReference<T>(
41
- bundlerConfig: SSRManifest,
61
+ bundlerConfig: SSRModuleMap,
62
metadata: ClientReferenceMetadata,
63
): ClientReference<T> {
44
- const moduleExports = bundlerConfig[metadata.id];
45
- let resolvedModuleData = moduleExports[metadata.name];
64
+ const moduleExports = bundlerConfig[metadata[ID]];
65
+ let resolvedModuleData = moduleExports[metadata[NAME]];
66
let name;
67
if (resolvedModuleData) {
68
// The potentially aliased name.
@@ -53,17 +73,17 @@ export function resolveClientReference<T>(
73
if (!resolvedModuleData) {
74
throw new Error(
75
'Could not find the module "' +
56
- metadata.id +
76
+ metadata[ID] +
77
'" in the React SSR Manifest. ' +
78
'This is probably a bug in the React Server Components bundler.',
79
);
80
}
61
- name = metadata.name;
81
+ name = metadata[NAME];
82
}
83
return {
84
specifier: resolvedModuleData.specifier,
85
name: name,
66
- async: metadata.async,
86
+ async: isAsyncImport(metadata),
87
};
88
}
89
packages/react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js
renamed
+66
-38
@@ -13,35 +13,62 @@ import type {
13
RejectedThenable,
14
} from 'shared/ReactTypes';
15
16
-export type SSRManifest = null | {
16
+import type {
17
+ ImportMetadata,
18
+ ImportManifestEntry,
19
+} from './shared/ReactFlightImportMetadata';
20
+import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
21
+
22
+import {
23
+ ID,
24
+ CHUNKS,
25
+ NAME,
26
+ isAsyncImport,
27
+} from './shared/ReactFlightImportMetadata';
28
+
29
+import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
30
+
31
+import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
32
+
33
+export type SSRModuleMap = null | {
34
[clientId: string]: {
18
- [clientExportName: string]: ClientReferenceMetadata,
35
+ [clientExportName: string]: ClientReferenceManifestEntry,
36
},
37
};
38
39
export type ServerManifest = {
23
- [id: string]: ClientReference<any>,
40
+ [id: string]: ImportManifestEntry,
41
};
42
43
export type ServerReferenceId = string;
44
28
-export opaque type ClientReferenceMetadata = {
29
- id: string,
30
- chunks: Array<string>,
31
- name: string,
32
- async: boolean,
33
-};
45
+export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
46
+export opaque type ClientReferenceMetadata = ImportMetadata;
47
48
// eslint-disable-next-line no-unused-vars
49
export opaque type ClientReference<T> = ClientReferenceMetadata;
50
51
+// The reason this function needs to defined here in this file instead of just
52
+// being exported directly from the WebpackDestination... file is because the
53
+// ClientReferenceMetadata is opaque and we can't unwrap it there.
54
+// This should get inlined and we could also just implement an unwrapping function
55
+// though that risks it getting used in places it shouldn't be. This is unfortunate
56
+// but currently it seems to be the best option we have.
57
+export function prepareDestinationForModule(
58
+ moduleLoading: ModuleLoading,
59
+ nonce: ?string,
60
+ metadata: ClientReferenceMetadata,
61
+) {
62
+ prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce);
63
+}
64
+
65
export function resolveClientReference<T>(
39
- bundlerConfig: SSRManifest,
66
+ bundlerConfig: SSRModuleMap,
67
metadata: ClientReferenceMetadata,
68
): ClientReference<T> {
69
if (bundlerConfig) {
43
- const moduleExports = bundlerConfig[metadata.id];
44
- let resolvedModuleData = moduleExports[metadata.name];
70
+ const moduleExports = bundlerConfig[metadata[ID]];
71
+ let resolvedModuleData = moduleExports[metadata[NAME]];
72
let name;
73
if (resolvedModuleData) {
74
// The potentially aliased name.
@@ -52,19 +79,23 @@ export function resolveClientReference<T>(
79
if (!resolvedModuleData) {
80
throw new Error(
81
'Could not find the module "' +
55
- metadata.id +
82
+ metadata[ID] +
83
'" in the React SSR Manifest. ' +
84
'This is probably a bug in the React Server Components bundler.',
85
);
86
}
60
- name = metadata.name;
87
+ name = metadata[NAME];
88
+ }
89
+ if (isAsyncImport(metadata)) {
90
+ return [
91
+ resolvedModuleData.id,
92
+ resolvedModuleData.chunks,
93
+ name,
94
+ 1 /* async */,
95
+ ];
96
+ } else {
97
+ return [resolvedModuleData.id, resolvedModuleData.chunks, name];
98
}
62
- return {
63
- id: resolvedModuleData.id,
64
- chunks: resolvedModuleData.chunks,
65
- name: name,
66
- async: !!metadata.async,
67
- };
99
}
100
return metadata;
101
}
@@ -98,12 +129,7 @@ export function resolveServerReference<T>(
129
}
130
}
131
// TODO: This needs to return async: true if it's an async module.
101
- return {
102
- id: resolvedModuleData.id,
103
- chunks: resolvedModuleData.chunks,
104
- name: name,
105
- async: false,
106
- };
132
+ return [resolvedModuleData.id, resolvedModuleData.chunks, name];
133
}
134
135
// The chunk cache contains all the chunks we've preloaded so far.
@@ -147,13 +173,15 @@ function ignoreReject() {
173
export function preloadModule<T>(
174
metadata: ClientReference<T>,
175
): null | Thenable<any> {
150
- const chunks = metadata.chunks;
176
+ const chunks = metadata[CHUNKS];
177
const promises = [];
152
- for (let i = 0; i < chunks.length; i++) {
153
- const chunkId = chunks[i];
178
+ let i = 0;
179
+ while (i < chunks.length) {
180
+ const chunkId = chunks[i++];
181
+ const chunkFilename = chunks[i++];
182
const entry = chunkCache.get(chunkId);
183
if (entry === undefined) {
156
- const thenable = __webpack_chunk_load__(chunkId);
184
+ const thenable = loadChunk(chunkId, chunkFilename);
185
promises.push(thenable);
186
// $FlowFixMe[method-unbinding]
187
const resolve = chunkCache.set.bind(chunkCache, chunkId, null);
@@ -163,12 +191,12 @@ export function preloadModule<T>(
191
promises.push(entry);
192
}
193
}
166
- if (metadata.async) {
194
+ if (isAsyncImport(metadata)) {
195
if (promises.length === 0) {
168
- return requireAsyncModule(metadata.id);
196
+ return requireAsyncModule(metadata[ID]);
197
} else {
198
return Promise.all(promises).then(() => {
171
- return requireAsyncModule(metadata.id);
199
+ return requireAsyncModule(metadata[ID]);
200
});
201
}
202
} else if (promises.length > 0) {
@@ -181,8 +209,8 @@ export function preloadModule<T>(
209
// Actually require the module or suspend if it's not yet ready.
210
// Increase priority if necessary.
211
export function requireModule<T>(metadata: ClientReference<T>): T {
184
- let moduleExports = __webpack_require__(metadata.id);
185
- if (metadata.async) {
212
+ let moduleExports = __webpack_require__(metadata[ID]);
213
+ if (isAsyncImport(metadata)) {
214
if (typeof moduleExports.then !== 'function') {
215
// This wasn't a promise after all.
216
} else if (moduleExports.status === 'fulfilled') {
@@ -192,15 +220,15 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
220
throw moduleExports.reason;
221
}
222
}
195
- if (metadata.name === '*') {
223
+ if (metadata[NAME] === '*') {
224
// This is a placeholder value that represents that the caller imported this
225
// as a CommonJS module as is.
226
return moduleExports;
227
}
200
- if (metadata.name === '') {
228
+ if (metadata[NAME] === '') {
229
// This is a placeholder value that represents that the caller accessed the
230
// default property of this if it was an ESM interop module.
231
return moduleExports.__esModule ? moduleExports.default : moduleExports;
232
}
205
- return moduleExports[metadata.name];
233
+ return moduleExports[metadata[NAME]];
234
}
packages/react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser.js
new
+28
@@ -0,0 +1,28 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+const chunkMap: Map<string, string> = new Map();
11
+
12
+/**
13
+ * We patch the chunk filename function in webpack to insert our own resolution
14
+ * of chunks that come from Flight and may not be known to the webpack runtime
15
+ */
16
+const webpackGetChunkFilename = __webpack_require__.u;
17
+__webpack_require__.u = function (chunkId: string) {
18
+ const flightChunk = chunkMap.get(chunkId);
19
+ if (flightChunk !== undefined) {
20
+ return flightChunk;
21
+ }
22
+ return webpackGetChunkFilename(chunkId);
23
+};
24
+
25
+export function loadChunk(chunkId: string, filename: string): Promise<mixed> {
26
+ chunkMap.set(chunkId, filename);
27
+ return __webpack_chunk_load__(chunkId);
28
+}
packages/react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer.js
new
+12
@@ -0,0 +1,12 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export function loadChunk(chunkId: string, filename: string): Promise<mixed> {
11
+ return __webpack_chunk_load__(chunkId);
12
+}
packages/react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackBrowser.js
new
+18
@@ -0,0 +1,18 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export type ModuleLoading = null;
11
+
12
+export function prepareDestinationWithChunks(
13
+ moduleLoading: ModuleLoading,
14
+ chunks: mixed,
15
+ nonce: ?string,
16
+) {
17
+ // In the browser we don't need to prepare our destination since the browser is the Destination
18
+}
packages/react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer.js
new
+32
@@ -0,0 +1,32 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import {preinitScriptForSSR} from 'react-client/src/ReactFlightClientConfig';
11
+
12
+export type ModuleLoading = null | {
13
+ prefix: string,
14
+ crossOrigin?: 'use-credentials' | '',
15
+};
16
+
17
+export function prepareDestinationWithChunks(
18
+ moduleLoading: ModuleLoading,
19
+ // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
20
+ chunks: Array<string>,
21
+ nonce: ?string,
22
+) {
23
+ if (moduleLoading !== null) {
24
+ for (let i = 1; i < chunks.length; i += 2) {
25
+ preinitScriptForSSR(
26
+ moduleLoading.prefix + chunks[i],
27
+ nonce,
28
+ moduleLoading.crossOrigin,
29
+ );
30
+ }
31
+ }
32
+}
packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js
+2
@@ -34,8 +34,10 @@ export type Options = {
34
35
function createResponseFromOptions(options: void | Options) {
36
return createResponse(
37
+ null,
38
null,
39
options && options.callServer ? options.callServer : undefined,
40
+ undefined, // nonce
41
);
42
}
43
packages/react-server-dom-webpack/src/ReactFlightDOMClientEdge.js
+17
-6
@@ -11,7 +11,15 @@ import type {Thenable} from 'shared/ReactTypes.js';
11
12
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
13
14
-import type {SSRManifest} from './ReactFlightClientConfigWebpackBundler';
14
+import type {
15
+ SSRModuleMap,
16
+ ModuleLoading,
17
+} from 'react-client/src/ReactFlightClientConfig';
18
+
19
+type SSRManifest = {
20
+ moduleMap: SSRModuleMap,
21
+ moduleLoading: ModuleLoading,
22
+};
23
24
import {
25
createResponse,
@@ -39,13 +47,16 @@ export function createServerReference<A: Iterable<any>, T>(
47
}
48
49
export type Options = {
42
- moduleMap?: $NonMaybeType<SSRManifest>,
50
+ ssrManifest: SSRManifest,
51
+ nonce?: string,
52
};
53
45
-function createResponseFromOptions(options: void | Options) {
54
+function createResponseFromOptions(options: Options) {
55
return createResponse(
47
- options && options.moduleMap ? options.moduleMap : null,
56
+ options.ssrManifest.moduleMap,
57
+ options.ssrManifest.moduleLoading,
58
noServerCall,
59
+ typeof options.nonce === 'string' ? options.nonce : undefined,
60
);
61
}
62
@@ -78,7 +89,7 @@ function startReadingFromStream(
89
90
function createFromReadableStream<T>(
91
stream: ReadableStream,
81
- options?: Options,
92
+ options: Options,
93
): Thenable<T> {
94
const response: FlightResponse = createResponseFromOptions(options);
95
startReadingFromStream(response, stream);
@@ -87,7 +98,7 @@ function createFromReadableStream<T>(
98
99
function createFromFetch<T>(
100
promiseForResponse: Promise<Response>,
90
- options?: Options,
101
+ options: Options,
102
): Thenable<T> {
103
const response: FlightResponse = createResponseFromOptions(options);
104
promiseForResponse.then(
packages/react-server-dom-webpack/src/ReactFlightDOMClientNode.js
+21
-3
@@ -11,7 +11,15 @@ import type {Thenable} from 'shared/ReactTypes.js';
11
12
import type {Response} from 'react-client/src/ReactFlightClient';
13
14
-import type {SSRManifest} from 'react-client/src/ReactFlightClientConfig';
14
+import type {
15
+ SSRModuleMap,
16
+ ModuleLoading,
17
+} from 'react-client/src/ReactFlightClientConfig';
18
+
19
+type SSRManifest = {
20
+ moduleMap: SSRModuleMap,
21
+ moduleLoading: ModuleLoading,
22
+};
23
24
import type {Readable} from 'stream';
25
@@ -40,11 +48,21 @@ export function createServerReference<A: Iterable<any>, T>(
48
return createServerReferenceImpl(id, noServerCall);
49
}
50
51
+export type Options = {
52
+ nonce?: string,
53
+};
54
+
55
function createFromNodeStream<T>(
56
stream: Readable,
45
- moduleMap: $NonMaybeType<SSRManifest>,
57
+ ssrManifest: SSRManifest,
58
+ options?: Options,
59
): Thenable<T> {
47
- const response: Response = createResponse(moduleMap, noServerCall);
60
+ const response: Response = createResponse(
61
+ ssrManifest.moduleMap,
62
+ ssrManifest.moduleLoading,
63
+ noServerCall,
64
+ options && typeof options.nonce === 'string' ? options.nonce : undefined,
65
+ );
66
stream.on('data', chunk => {
67
processBinaryChunk(response, chunk);
68
});
packages/react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler.js
+12
-13
@@ -8,6 +8,10 @@
8
*/
9
10
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
11
+import type {
12
+ ImportMetadata,
13
+ ImportManifestEntry,
14
+} from './shared/ReactFlightImportMetadata';
15
16
import type {
17
ClientReference,
@@ -17,17 +21,13 @@ import type {
21
export type {ClientReference, ServerReference};
22
23
export type ClientManifest = {
20
- [id: string]: ClientReferenceMetadata,
24
+ [id: string]: ClientReferenceManifestEntry,
25
};
26
27
export type ServerReferenceId = string;
28
25
-export type ClientReferenceMetadata = {
26
- id: string,
27
- chunks: Array<string>,
28
- name: string,
29
- async: boolean,
30
-};
29
+export type ClientReferenceMetadata = ImportMetadata;
30
+export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
31
32
export type ClientReferenceKey = string;
33
@@ -71,12 +71,11 @@ export function resolveClientReferenceMetadata<T>(
71
);
72
}
73
}
74
- return {
75
- id: resolvedModuleData.id,
76
- chunks: resolvedModuleData.chunks,
77
- name: name,
78
- async: !!clientReference.$$async,
79
- };
74
+ if (clientReference.$$async === true) {
75
+ return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
76
+ } else {
77
+ return [resolvedModuleData.id, resolvedModuleData.chunks, name];
78
+ }
79
}
80
81
export function getServerReferenceId<T>(
packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js
+56
-10
@@ -7,6 +7,8 @@
7
* @flow
8
*/
9
10
+import type {ImportManifestEntry} from './shared/ReactFlightImportMetadata';
11
+
12
import {join} from 'path';
13
import {pathToFileURL} from 'url';
14
import asyncLib from 'neo-async';
@@ -221,21 +223,65 @@ export default class ReactFlightWebpackPlugin {
223
return;
224
}
225
226
+ const configuredCrossOriginLoading =
227
+ compilation.outputOptions.crossOriginLoading;
228
+ const crossOriginMode =
229
+ typeof configuredCrossOriginLoading === 'string'
230
+ ? configuredCrossOriginLoading === 'use-credentials'
231
+ ? configuredCrossOriginLoading
232
+ : 'anonymous'
233
+ : null;
234
+
235
const resolvedClientFiles = new Set(
236
(resolvedClientReferences || []).map(ref => ref.request),
237
);
238
239
const clientManifest: {
229
- [string]: {chunks: $FlowFixMe, id: string, name: string},
240
+ [string]: ImportManifestEntry,
241
} = {};
231
- const ssrManifest: {
242
+ type SSRModuleMap = {
243
[string]: {
244
[string]: {specifier: string, name: string},
245
},
235
- } = {};
246
+ };
247
+ const moduleMap: SSRModuleMap = {};
248
+ const ssrBundleConfig: {
249
+ moduleLoading: {
250
+ prefix: string,
251
+ crossOrigin: string | null,
252
+ },
253
+ moduleMap: SSRModuleMap,
254
+ } = {
255
+ moduleLoading: {
256
+ prefix: compilation.outputOptions.publicPath || '',
257
+ crossOrigin: crossOriginMode,
258
+ },
259
+ moduleMap,
260
+ };
261
+
262
+ // We figure out which files are always loaded by any initial chunk (entrypoint).
263
+ // We use this to filter out chunks that Flight will never need to load
264
+ const emptySet: Set<string> = new Set();
265
+ const runtimeChunkFiles: Set<string> = emptySet;
266
+ compilation.entrypoints.forEach(entrypoint => {
267
+ const runtimeChunk = entrypoint.getRuntimeChunk();
268
+ if (runtimeChunk) {
269
+ runtimeChunk.files.forEach(runtimeFile => {
270
+ runtimeChunkFiles.add(runtimeFile);
271
+ });
272
+ }
273
+ });
274
+
275
compilation.chunkGroups.forEach(function (chunkGroup) {
237
- const chunkIds = chunkGroup.chunks.map(function (c) {
238
- return c.id;
276
+ const chunks: Array<string> = [];
277
+ chunkGroup.chunks.forEach(function (c) {
278
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
279
+ for (const file of c.files) {
280
+ if (!file.endsWith('.js')) return;
281
+ if (file.endsWith('.hot-update.js')) return;
282
+ chunks.push(c.id, file);
283
+ break;
284
+ }
285
});
286
287
// $FlowFixMe[missing-local-annot]
@@ -256,7 +302,7 @@ export default class ReactFlightWebpackPlugin {
302
303
clientManifest[href] = {
304
id,
259
- chunks: chunkIds,
305
+ chunks,
306
name: '*',
307
};
308
ssrExports['*'] = {
@@ -272,7 +318,7 @@ export default class ReactFlightWebpackPlugin {
318
/*
319
clientManifest[href + '#'] = {
320
id,
275
- chunks: chunkIds,
321
+ chunks,
322
name: '',
323
};
324
ssrExports[''] = {
@@ -288,7 +334,7 @@ export default class ReactFlightWebpackPlugin {
334
moduleProvidedExports.forEach(function (name) {
335
clientManifest[href + '#' + name] = {
336
id,
291
- chunks: chunkIds,
337
+ chunks,
338
name: name,
339
};
340
ssrExports[name] = {
@@ -299,7 +345,7 @@ export default class ReactFlightWebpackPlugin {
345
}
346
*/
347
302
- ssrManifest[id] = ssrExports;
348
+ moduleMap[id] = ssrExports;
349
}
350
}
351
@@ -326,7 +372,7 @@ export default class ReactFlightWebpackPlugin {
372
_this.clientManifestFilename,
373
new sources.RawSource(clientOutput, false),
374
);
329
- const ssrOutput = JSON.stringify(ssrManifest, null, 2);
375
+ const ssrOutput = JSON.stringify(ssrBundleConfig, null, 2);
376
compilation.emitAsset(
377
_this.ssrManifestFilename,
378
new sources.RawSource(ssrOutput, false),
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+70
-60
@@ -25,8 +25,9 @@ let clientExports;
25
let clientModuleError;
26
let webpackMap;
27
let Stream;
28
+let FlightReact;
29
let React;
29
-let ReactDOM;
30
+let FlightReactDOM;
31
let ReactDOMClient;
32
let ReactServerDOMServer;
33
let ReactServerDOMClient;
@@ -37,6 +38,9 @@ let JSDOM;
38
39
describe('ReactFlightDOM', () => {
40
beforeEach(() => {
41
+ // For this first reset we are going to load the dom-node version of react-server-dom-webpack/server
42
+ // This can be thought of as essentially being the React Server Components scope with react-server
43
+ // condition
44
jest.resetModules();
45
46
JSDOM = require('jsdom').JSDOM;
@@ -45,23 +49,29 @@ describe('ReactFlightDOM', () => {
49
jest.mock('react-server-dom-webpack/server', () =>
50
require('react-server-dom-webpack/server.node.unbundled'),
51
);
52
+ jest.mock('react', () => require('react/react.shared-subset'));
53
49
- ReactServerDOMClient = require('react-server-dom-webpack/client');
50
-
51
- act = require('internal-test-utils').act;
54
const WebpackMock = require('./utils/WebpackMock');
55
clientExports = WebpackMock.clientExports;
56
clientModuleError = WebpackMock.clientModuleError;
57
webpackMap = WebpackMock.webpackMap;
58
59
+ ReactServerDOMServer = require('react-server-dom-webpack/server');
60
+ FlightReact = require('react');
61
+ FlightReactDOM = require('react-dom');
62
+
63
+ // This reset is to load modules for the SSR/Browser scope.
64
+ jest.unmock('react-server-dom-webpack/server');
65
+ jest.unmock('react');
66
+ jest.resetModules();
67
+ act = require('internal-test-utils').act;
68
Stream = require('stream');
69
React = require('react');
59
- ReactDOM = require('react-dom');
60
- ReactDOMFizzServer = require('react-dom/server.node');
70
use = React.use;
71
Suspense = React.Suspense;
72
ReactDOMClient = require('react-dom/client');
64
- ReactServerDOMServer = require('react-server-dom-webpack/server.node.unbundled');
73
+ ReactDOMFizzServer = require('react-dom/server.node');
74
+ ReactServerDOMClient = require('react-server-dom-webpack/client');
75
76
ErrorBoundary = class extends React.Component {
77
state = {hasError: false, error: null};
@@ -485,7 +495,7 @@ describe('ReactFlightDOM', () => {
495
const AsyncModuleRef = clientExports(AsyncModule);
496
497
function ServerComponent() {
488
- const text = use(AsyncModuleRef);
498
+ const text = FlightReact.use(AsyncModuleRef);
499
return <p>{text}</p>;
500
}
501
@@ -1205,25 +1215,25 @@ describe('ReactFlightDOM', () => {
1215
const ClientComponent = clientExports(Component);
1216
1217
async function ServerComponent() {
1208
- ReactDOM.prefetchDNS('d before');
1209
- ReactDOM.preconnect('c before');
1210
- ReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1211
- ReactDOM.preload('l before', {as: 'style'});
1212
- ReactDOM.preloadModule('lm before');
1213
- ReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1214
- ReactDOM.preinit('i before', {as: 'script'});
1215
- ReactDOM.preinitModule('m before');
1216
- ReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1218
+ FlightReactDOM.prefetchDNS('d before');
1219
+ FlightReactDOM.preconnect('c before');
1220
+ FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1221
+ FlightReactDOM.preload('l before', {as: 'style'});
1222
+ FlightReactDOM.preloadModule('lm before');
1223
+ FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1224
+ FlightReactDOM.preinit('i before', {as: 'script'});
1225
+ FlightReactDOM.preinitModule('m before');
1226
+ FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1227
await 1;
1218
- ReactDOM.prefetchDNS('d after');
1219
- ReactDOM.preconnect('c after');
1220
- ReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1221
- ReactDOM.preload('l after', {as: 'style'});
1222
- ReactDOM.preloadModule('lm after');
1223
- ReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1224
- ReactDOM.preinit('i after', {as: 'script'});
1225
- ReactDOM.preinitModule('m after');
1226
- ReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1228
+ FlightReactDOM.prefetchDNS('d after');
1229
+ FlightReactDOM.preconnect('c after');
1230
+ FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1231
+ FlightReactDOM.preload('l after', {as: 'style'});
1232
+ FlightReactDOM.preloadModule('lm after');
1233
+ FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1234
+ FlightReactDOM.preinit('i after', {as: 'script'});
1235
+ FlightReactDOM.preinitModule('m after');
1236
+ FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1237
return <ClientComponent />;
1238
}
1239
@@ -1298,25 +1308,25 @@ describe('ReactFlightDOM', () => {
1308
const ClientComponent = clientExports(Component);
1309
1310
async function ServerComponent() {
1301
- ReactDOM.prefetchDNS('d before');
1302
- ReactDOM.preconnect('c before');
1303
- ReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1304
- ReactDOM.preload('l before', {as: 'style'});
1305
- ReactDOM.preloadModule('lm before');
1306
- ReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1307
- ReactDOM.preinit('i before', {as: 'script'});
1308
- ReactDOM.preinitModule('m before');
1309
- ReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1311
+ FlightReactDOM.prefetchDNS('d before');
1312
+ FlightReactDOM.preconnect('c before');
1313
+ FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1314
+ FlightReactDOM.preload('l before', {as: 'style'});
1315
+ FlightReactDOM.preloadModule('lm before');
1316
+ FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1317
+ FlightReactDOM.preinit('i before', {as: 'script'});
1318
+ FlightReactDOM.preinitModule('m before');
1319
+ FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1320
await 1;
1311
- ReactDOM.prefetchDNS('d after');
1312
- ReactDOM.preconnect('c after');
1313
- ReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1314
- ReactDOM.preload('l after', {as: 'style'});
1315
- ReactDOM.preloadModule('lm after');
1316
- ReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1317
- ReactDOM.preinit('i after', {as: 'script'});
1318
- ReactDOM.preinitModule('m after');
1319
- ReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1321
+ FlightReactDOM.prefetchDNS('d after');
1322
+ FlightReactDOM.preconnect('c after');
1323
+ FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1324
+ FlightReactDOM.preload('l after', {as: 'style'});
1325
+ FlightReactDOM.preloadModule('lm after');
1326
+ FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1327
+ FlightReactDOM.preinit('i after', {as: 'script'});
1328
+ FlightReactDOM.preinitModule('m after');
1329
+ FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1330
return <ClientComponent />;
1331
}
1332
@@ -1406,16 +1416,16 @@ describe('ReactFlightDOM', () => {
1416
const ClientComponent = clientExports(Component);
1417
1418
async function ServerComponent1() {
1409
- ReactDOM.preload('before1', {as: 'style'});
1419
+ FlightReactDOM.preload('before1', {as: 'style'});
1420
await 1;
1411
- ReactDOM.preload('after1', {as: 'style'});
1421
+ FlightReactDOM.preload('after1', {as: 'style'});
1422
return <ClientComponent />;
1423
}
1424
1425
async function ServerComponent2() {
1416
- ReactDOM.preload('before2', {as: 'style'});
1426
+ FlightReactDOM.preload('before2', {as: 'style'});
1427
await 1;
1418
- ReactDOM.preload('after2', {as: 'style'});
1428
+ FlightReactDOM.preload('after2', {as: 'style'});
1429
return <ClientComponent />;
1430
}
1431
@@ -1506,21 +1516,21 @@ describe('ReactFlightDOM', () => {
1516
const ClientComponent = clientExports(Component);
1517
1518
async function ServerComponent() {
1509
- ReactDOM.prefetchDNS('dns');
1510
- ReactDOM.preconnect('preconnect');
1511
- ReactDOM.preload('load', {as: 'style'});
1512
- ReactDOM.preinit('init', {as: 'script'});
1519
+ FlightReactDOM.prefetchDNS('dns');
1520
+ FlightReactDOM.preconnect('preconnect');
1521
+ FlightReactDOM.preload('load', {as: 'style'});
1522
+ FlightReactDOM.preinit('init', {as: 'script'});
1523
// again but vary preconnect to demonstrate crossOrigin participates in the key
1514
- ReactDOM.prefetchDNS('dns');
1515
- ReactDOM.preconnect('preconnect', {crossOrigin: 'anonymous'});
1516
- ReactDOM.preload('load', {as: 'style'});
1517
- ReactDOM.preinit('init', {as: 'script'});
1524
+ FlightReactDOM.prefetchDNS('dns');
1525
+ FlightReactDOM.preconnect('preconnect', {crossOrigin: 'anonymous'});
1526
+ FlightReactDOM.preload('load', {as: 'style'});
1527
+ FlightReactDOM.preinit('init', {as: 'script'});
1528
await 1;
1529
// after an async point
1520
- ReactDOM.prefetchDNS('dns');
1521
- ReactDOM.preconnect('preconnect', {crossOrigin: 'use-credentials'});
1522
- ReactDOM.preload('load', {as: 'style'});
1523
- ReactDOM.preinit('init', {as: 'script'});
1530
+ FlightReactDOM.prefetchDNS('dns');
1531
+ FlightReactDOM.preconnect('preconnect', {crossOrigin: 'use-credentials'});
1532
+ FlightReactDOM.preload('load', {as: 'style'});
1533
+ FlightReactDOM.preinit('init', {as: 'script'});
1534
return <ClientComponent />;
1535
}
1536
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+32
-5
@@ -22,6 +22,7 @@ global.setTimeout = cb => cb();
22
let clientExports;
23
let webpackMap;
24
let webpackModules;
25
+let webpackModuleLoading;
26
let React;
27
let ReactDOMServer;
28
let ReactServerDOMServer;
@@ -33,18 +34,28 @@ describe('ReactFlightDOMEdge', () => {
34
jest.resetModules();
35
36
// Simulate the condition resolution
37
+ jest.mock('react', () => require('react/react.shared-subset'));
38
jest.mock('react-server-dom-webpack/server', () =>
39
require('react-server-dom-webpack/server.edge'),
40
);
41
+ ReactServerDOMServer = require('react-server-dom-webpack/server');
42
43
const WebpackMock = require('./utils/WebpackMock');
44
+
45
clientExports = WebpackMock.clientExports;
46
webpackMap = WebpackMock.webpackMap;
47
webpackModules = WebpackMock.webpackModules;
48
+ webpackModuleLoading = WebpackMock.moduleLoading;
49
+
50
+ jest.resetModules();
51
+ jest.unmock('react');
52
+ jest.unmock('react-server-dom-webpack/server');
53
+ jest.mock('react-server-dom-webpack/client', () =>
54
+ require('react-server-dom-webpack/client.edge'),
55
+ );
56
React = require('react');
57
ReactDOMServer = require('react-dom/server.edge');
46
- ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
47
- ReactServerDOMClient = require('react-server-dom-webpack/client.edge');
58
+ ReactServerDOMClient = require('react-server-dom-webpack/client');
59
use = React.use;
60
});
61
@@ -122,7 +133,10 @@ describe('ReactFlightDOMEdge', () => {
133
webpackMap,
134
);
135
const response = ReactServerDOMClient.createFromReadableStream(stream, {
125
- moduleMap: translationMap,
136
+ ssrManifest: {
137
+ moduleMap: translationMap,
138
+ moduleLoading: webpackModuleLoading,
139
+ },
140
});
141
142
function ClientRoot() {
@@ -154,7 +168,15 @@ describe('ReactFlightDOMEdge', () => {
168
expect(serializedContent).not.toContain('\\"');
169
expect(serializedContent).toContain('\t');
170
157
- const result = await ReactServerDOMClient.createFromReadableStream(stream2);
171
+ const result = await ReactServerDOMClient.createFromReadableStream(
172
+ stream2,
173
+ {
174
+ ssrManifest: {
175
+ moduleMap: null,
176
+ moduleLoading: null,
177
+ },
178
+ },
179
+ );
180
// Should still match the result when parsed
181
expect(result.text).toBe(testString);
182
expect(result.text2).toBe(testString2);
@@ -183,7 +205,12 @@ describe('ReactFlightDOMEdge', () => {
205
const stream = passThrough(
206
ReactServerDOMServer.renderToReadableStream(buffers),
207
);
186
- const result = await ReactServerDOMClient.createFromReadableStream(stream);
208
+ const result = await ReactServerDOMClient.createFromReadableStream(stream, {
209
+ ssrManifest: {
210
+ moduleMap: null,
211
+ moduleLoading: null,
212
+ },
213
+ });
214
expect(result).toEqual(buffers);
215
});
216
});
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
+123
-23
@@ -140,7 +140,12 @@ describe('ReactFlightDOMForm', () => {
140
);
141
}
142
const rscStream = ReactServerDOMServer.renderToReadableStream(<App />);
143
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
143
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
144
+ ssrManifest: {
145
+ moduleMap: null,
146
+ moduleLoading: null,
147
+ },
148
+ });
149
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
150
await readIntoContainer(ssrStream);
151
@@ -203,7 +208,12 @@ describe('ReactFlightDOMForm', () => {
208
);
209
}
210
const rscStream = ReactServerDOMServer.renderToReadableStream(<App />);
206
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
211
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
212
+ ssrManifest: {
213
+ moduleMap: null,
214
+ moduleLoading: null,
215
+ },
216
+ });
217
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
218
await readIntoContainer(ssrStream);
219
@@ -239,7 +249,12 @@ describe('ReactFlightDOMForm', () => {
249
);
250
}
251
const rscStream = ReactServerDOMServer.renderToReadableStream(<App />);
242
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
252
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
253
+ ssrManifest: {
254
+ moduleMap: null,
255
+ moduleLoading: null,
256
+ },
257
+ });
258
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
259
await readIntoContainer(ssrStream);
260
@@ -307,7 +322,12 @@ describe('ReactFlightDOMForm', () => {
322
<ClientRef action={serverAction} />,
323
webpackMap,
324
);
310
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
325
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
326
+ ssrManifest: {
327
+ moduleMap: null,
328
+ moduleLoading: null,
329
+ },
330
+ });
331
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
332
await readIntoContainer(ssrStream);
333
@@ -349,7 +369,12 @@ describe('ReactFlightDOMForm', () => {
369
<ClientRef action={serverAction} />,
370
webpackMap,
371
);
352
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
372
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
373
+ ssrManifest: {
374
+ moduleMap: null,
375
+ moduleLoading: null,
376
+ },
377
+ });
378
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
379
await readIntoContainer(ssrStream);
380
@@ -392,7 +417,12 @@ describe('ReactFlightDOMForm', () => {
417
<ClientRef action={serverAction} />,
418
webpackMap,
419
);
395
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
420
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
421
+ ssrManifest: {
422
+ moduleMap: null,
423
+ moduleLoading: null,
424
+ },
425
+ });
426
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
427
await readIntoContainer(ssrStream);
428
@@ -410,8 +440,15 @@ describe('ReactFlightDOMForm', () => {
440
<ClientRef action={serverAction} />,
441
webpackMap,
442
);
413
- const postbackResponse =
414
- ReactServerDOMClient.createFromReadableStream(postbackRscStream);
443
+ const postbackResponse = ReactServerDOMClient.createFromReadableStream(
444
+ postbackRscStream,
445
+ {
446
+ ssrManifest: {
447
+ moduleMap: null,
448
+ moduleLoading: null,
449
+ },
450
+ },
451
+ );
452
const postbackSsrStream = await ReactDOMServer.renderToReadableStream(
453
postbackResponse,
454
{experimental_formState: formState},
@@ -471,7 +508,15 @@ describe('ReactFlightDOMForm', () => {
508
<ClientRef action={serverAction.bind(null, 1)} />,
509
webpackMap,
510
);
474
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
511
+ const response = ReactServerDOMClient.createFromReadableStream(
512
+ rscStream,
513
+ {
514
+ ssrManifest: {
515
+ moduleMap: null,
516
+ moduleLoading: null,
517
+ },
518
+ },
519
+ );
520
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
521
await readIntoContainer(ssrStream);
522
@@ -493,8 +538,15 @@ describe('ReactFlightDOMForm', () => {
538
<ClientRef action={serverAction.bind(null, 5)} />,
539
webpackMap,
540
);
496
- const postbackResponse =
497
- ReactServerDOMClient.createFromReadableStream(postbackRscStream);
541
+ const postbackResponse = ReactServerDOMClient.createFromReadableStream(
542
+ postbackRscStream,
543
+ {
544
+ ssrManifest: {
545
+ moduleMap: null,
546
+ moduleLoading: null,
547
+ },
548
+ },
549
+ );
550
const postbackSsrStream = await ReactDOMServer.renderToReadableStream(
551
postbackResponse,
552
{experimental_formState: formState},
@@ -518,8 +570,15 @@ describe('ReactFlightDOMForm', () => {
570
<ClientRef action={serverAction.bind(null, 5)} />,
571
webpackMap,
572
);
521
- const postbackResponse2 =
522
- ReactServerDOMClient.createFromReadableStream(postbackRscStream2);
573
+ const postbackResponse2 = ReactServerDOMClient.createFromReadableStream(
574
+ postbackRscStream2,
575
+ {
576
+ ssrManifest: {
577
+ moduleMap: null,
578
+ moduleLoading: null,
579
+ },
580
+ },
581
+ );
582
const postbackSsrStream2 = await ReactDOMServer.renderToReadableStream(
583
postbackResponse2,
584
{experimental_formState: formState2},
@@ -570,7 +629,12 @@ describe('ReactFlightDOMForm', () => {
629
<ClientRef action={increaseBy1} />,
630
webpackMap,
631
);
573
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
632
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
633
+ ssrManifest: {
634
+ moduleMap: null,
635
+ moduleLoading: null,
636
+ },
637
+ });
638
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
639
await readIntoContainer(ssrStream);
640
@@ -590,8 +654,15 @@ describe('ReactFlightDOMForm', () => {
654
<ClientRef action={increaseBy5} />,
655
webpackMap,
656
);
593
- const postbackResponse =
594
- ReactServerDOMClient.createFromReadableStream(postbackRscStream);
657
+ const postbackResponse = ReactServerDOMClient.createFromReadableStream(
658
+ postbackRscStream,
659
+ {
660
+ ssrManifest: {
661
+ moduleMap: null,
662
+ moduleLoading: null,
663
+ },
664
+ },
665
+ );
666
const postbackSsrStream = await ReactDOMServer.renderToReadableStream(
667
postbackResponse,
668
{experimental_formState: formState},
@@ -633,7 +704,12 @@ describe('ReactFlightDOMForm', () => {
704
<Page1Ref action={serverAction} permalink="/permalink" />,
705
webpackMap,
706
);
636
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
707
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
708
+ ssrManifest: {
709
+ moduleMap: null,
710
+ moduleLoading: null,
711
+ },
712
+ });
713
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
714
await readIntoContainer(ssrStream);
715
@@ -654,8 +730,15 @@ describe('ReactFlightDOMForm', () => {
730
<Page2Ref action={serverAction} permalink="/permalink" />,
731
webpackMap,
732
);
657
- const postbackResponse =
658
- ReactServerDOMClient.createFromReadableStream(postbackRscStream);
733
+ const postbackResponse = ReactServerDOMClient.createFromReadableStream(
734
+ postbackRscStream,
735
+ {
736
+ ssrManifest: {
737
+ moduleMap: null,
738
+ moduleLoading: null,
739
+ },
740
+ },
741
+ );
742
const postbackSsrStream = await ReactDOMServer.renderToReadableStream(
743
postbackResponse,
744
{experimental_formState: formState},
@@ -675,8 +758,15 @@ describe('ReactFlightDOMForm', () => {
758
<Page1Ref action={serverAction} permalink="/some-other-permalink" />,
759
webpackMap,
760
);
678
- const postbackResponse2 =
679
- ReactServerDOMClient.createFromReadableStream(postbackRscStream2);
761
+ const postbackResponse2 = ReactServerDOMClient.createFromReadableStream(
762
+ postbackRscStream2,
763
+ {
764
+ ssrManifest: {
765
+ moduleMap: null,
766
+ moduleLoading: null,
767
+ },
768
+ },
769
+ );
770
const postbackSsrStream2 = await ReactDOMServer.renderToReadableStream(
771
postbackResponse2,
772
{experimental_formState: formState2},
@@ -713,7 +803,12 @@ describe('ReactFlightDOMForm', () => {
803
<ClientRef action={serverAction} />,
804
webpackMap,
805
);
716
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
806
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
807
+ ssrManifest: {
808
+ moduleMap: null,
809
+ moduleLoading: null,
810
+ },
811
+ });
812
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
813
await readIntoContainer(ssrStream);
814
@@ -754,7 +849,12 @@ describe('ReactFlightDOMForm', () => {
849
<ClientRef action={serverAction} />,
850
webpackMap,
851
);
757
- const response = ReactServerDOMClient.createFromReadableStream(rscStream);
852
+ const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
853
+ ssrManifest: {
854
+ moduleMap: null,
855
+ moduleLoading: null,
856
+ },
857
+ });
858
const ssrStream = await ReactDOMServer.renderToReadableStream(response);
859
await readIntoContainer(ssrStream);
860
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
+103
-10
@@ -16,6 +16,7 @@ global.setImmediate = cb => cb();
16
let clientExports;
17
let webpackMap;
18
let webpackModules;
19
+let webpackModuleLoading;
20
let React;
21
let ReactDOMServer;
22
let ReactServerDOMServer;
@@ -28,18 +29,28 @@ describe('ReactFlightDOMNode', () => {
29
jest.resetModules();
30
31
// Simulate the condition resolution
32
+ jest.mock('react', () => require('react/react.shared-subset'));
33
jest.mock('react-server-dom-webpack/server', () =>
34
require('react-server-dom-webpack/server.node'),
35
);
36
+ ReactServerDOMServer = require('react-server-dom-webpack/server');
37
38
const WebpackMock = require('./utils/WebpackMock');
39
clientExports = WebpackMock.clientExports;
40
webpackMap = WebpackMock.webpackMap;
41
webpackModules = WebpackMock.webpackModules;
42
+ webpackModuleLoading = WebpackMock.moduleLoading;
43
+
44
+ jest.resetModules();
45
+ jest.unmock('react');
46
+ jest.unmock('react-server-dom-webpack/server');
47
+ jest.mock('react-server-dom-webpack/client', () =>
48
+ require('react-server-dom-webpack/client.node'),
49
+ );
50
+
51
React = require('react');
52
ReactDOMServer = require('react-dom/server.node');
41
- ReactServerDOMServer = require('react-server-dom-webpack/server.node');
42
- ReactServerDOMClient = require('react-server-dom-webpack/client.node');
53
+ ReactServerDOMClient = require('react-server-dom-webpack/client');
54
Stream = require('stream');
55
use = React.use;
56
});
@@ -68,7 +79,11 @@ describe('ReactFlightDOMNode', () => {
79
}
80
// The Client build may not have the same IDs as the Server bundles for the same
81
// component.
71
- const ClientComponentOnTheClient = clientExports(ClientComponent);
82
+ const ClientComponentOnTheClient = clientExports(
83
+ ClientComponent,
84
+ 123,
85
+ 'path/to/chunk.js',
86
+ );
87
const ClientComponentOnTheServer = clientExports(ClientComponent);
88
89
// In the SSR bundle this module won't exist. We simulate this by deleting it.
@@ -83,6 +98,10 @@ describe('ReactFlightDOMNode', () => {
98
'*': ssrMetadata,
99
},
100
};
101
+ const ssrManifest = {
102
+ moduleMap: translationMap,
103
+ moduleLoading: webpackModuleLoading,
104
+ };
105
106
function App() {
107
return <ClientComponentOnTheClient />;
@@ -93,14 +112,16 @@ describe('ReactFlightDOMNode', () => {
112
webpackMap,
113
);
114
const readable = new Stream.PassThrough();
96
- const response = ReactServerDOMClient.createFromNodeStream(
97
- readable,
98
- translationMap,
99
- );
115
+ let response;
116
117
stream.pipe(readable);
118
119
function ClientRoot() {
120
+ if (response) return use(response);
121
+ response = ReactServerDOMClient.createFromNodeStream(
122
+ readable,
123
+ ssrManifest,
124
+ );
125
return use(response);
126
}
127
@@ -108,7 +129,9 @@ describe('ReactFlightDOMNode', () => {
129
<ClientRoot />,
130
);
131
const result = await readResult(ssrStream);
111
- expect(result).toEqual('<span>Client Component</span>');
132
+ expect(result).toEqual(
133
+ '<script src="/path/to/chunk.js" async=""></script><span>Client Component</span>',
134
+ );
135
});
136
137
it('should encode long string in a compact format', async () => {
@@ -121,7 +144,10 @@ describe('ReactFlightDOMNode', () => {
144
const readable = new Stream.PassThrough();
145
146
const stringResult = readResult(readable);
124
- const parsedResult = ReactServerDOMClient.createFromNodeStream(readable);
147
+ const parsedResult = ReactServerDOMClient.createFromNodeStream(readable, {
148
+ moduleMap: {},
149
+ moduleLoading: webpackModuleLoading,
150
+ });
151
152
stream.pipe(readable);
153
@@ -160,9 +186,76 @@ describe('ReactFlightDOMNode', () => {
186
];
187
const stream = ReactServerDOMServer.renderToPipeableStream(buffers);
188
const readable = new Stream.PassThrough();
163
- const promise = ReactServerDOMClient.createFromNodeStream(readable);
189
+ const promise = ReactServerDOMClient.createFromNodeStream(readable, {
190
+ moduleMap: {},
191
+ moduleLoading: webpackModuleLoading,
192
+ });
193
stream.pipe(readable);
194
const result = await promise;
195
expect(result).toEqual(buffers);
196
});
197
+
198
+ it('should allow accept a nonce option for Flight preinitialized scripts', async () => {
199
+ function ClientComponent() {
200
+ return <span>Client Component</span>;
201
+ }
202
+ // The Client build may not have the same IDs as the Server bundles for the same
203
+ // component.
204
+ const ClientComponentOnTheClient = clientExports(
205
+ ClientComponent,
206
+ 123,
207
+ 'path/to/chunk.js',
208
+ );
209
+ const ClientComponentOnTheServer = clientExports(ClientComponent);
210
+
211
+ // In the SSR bundle this module won't exist. We simulate this by deleting it.
212
+ const clientId = webpackMap[ClientComponentOnTheClient.$$id].id;
213
+ delete webpackModules[clientId];
214
+
215
+ // Instead, we have to provide a translation from the client meta data to the SSR
216
+ // meta data.
217
+ const ssrMetadata = webpackMap[ClientComponentOnTheServer.$$id];
218
+ const translationMap = {
219
+ [clientId]: {
220
+ '*': ssrMetadata,
221
+ },
222
+ };
223
+ const ssrManifest = {
224
+ moduleMap: translationMap,
225
+ moduleLoading: webpackModuleLoading,
226
+ };
227
+
228
+ function App() {
229
+ return <ClientComponentOnTheClient />;
230
+ }
231
+
232
+ const stream = ReactServerDOMServer.renderToPipeableStream(
233
+ <App />,
234
+ webpackMap,
235
+ );
236
+ const readable = new Stream.PassThrough();
237
+ let response;
238
+
239
+ stream.pipe(readable);
240
+
241
+ function ClientRoot() {
242
+ if (response) return use(response);
243
+ response = ReactServerDOMClient.createFromNodeStream(
244
+ readable,
245
+ ssrManifest,
246
+ {
247
+ nonce: 'r4nd0m',
248
+ },
249
+ );
250
+ return use(response);
251
+ }
252
+
253
+ const ssrStream = await ReactDOMServer.renderToPipeableStream(
254
+ <ClientRoot />,
255
+ );
256
+ const result = await readResult(ssrStream);
257
+ expect(result).toEqual(
258
+ '<script src="/path/to/chunk.js" async="" nonce="r4nd0m"></script><span>Client Component</span>',
259
+ );
260
+ });
261
});
packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js
+16
-5
@@ -42,6 +42,9 @@ Module.prototype._compile = previousCompile;
42
exports.webpackMap = webpackClientMap;
43
exports.webpackModules = webpackClientModules;
44
exports.webpackServerMap = webpackServerMap;
45
+exports.moduleLoading = {
46
+ prefix: '/',
47
+};
48
49
exports.clientModuleError = function clientModuleError(moduleError) {
50
const idx = '' + webpackModuleIdx++;
@@ -57,20 +60,28 @@ exports.clientModuleError = function clientModuleError(moduleError) {
60
return mod.exports;
61
};
62
60
-exports.clientExports = function clientExports(moduleExports) {
63
+exports.clientExports = function clientExports(
64
+ moduleExports,
65
+ chunkId,
66
+ chunkFilename,
67
+) {
68
+ const chunks = [];
69
+ if (chunkId) {
70
+ chunks.push(chunkId, chunkFilename);
71
+ }
72
const idx = '' + webpackModuleIdx++;
73
webpackClientModules[idx] = moduleExports;
74
const path = url.pathToFileURL(idx).href;
75
webpackClientMap[path] = {
76
id: idx,
66
- chunks: [],
77
+ chunks,
78
name: '*',
79
};
80
// We only add this if this test is testing ESM compat.
81
if ('__esModule' in moduleExports) {
82
webpackClientMap[path + '#'] = {
83
id: idx,
73
- chunks: [],
84
+ chunks,
85
name: '',
86
};
87
}
@@ -80,7 +91,7 @@ exports.clientExports = function clientExports(moduleExports) {
91
for (const name in asyncModuleExports) {
92
webpackClientMap[path + '#' + name] = {
93
id: idx,
83
- chunks: [],
94
+ chunks,
95
name: name,
96
};
97
}
@@ -96,7 +107,7 @@ exports.clientExports = function clientExports(moduleExports) {
107
};
108
webpackClientMap[path + '#split'] = {
109
id: splitIdx,
99
- chunks: [],
110
+ chunks,
111
name: 's',
112
};
113
}
packages/react-server-dom-webpack/src/shared/ReactFlightImportMetadata.js
new
+43
@@ -0,0 +1,43 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+export type ImportManifestEntry = {
11
+ id: string,
12
+ // chunks is a double indexed array of chunkId / chunkFilename pairs
13
+ chunks: Array<string>,
14
+ name: string,
15
+};
16
+
17
+// This is the parsed shape of the wire format which is why it is
18
+// condensed to only the essentialy information
19
+export type ImportMetadata =
20
+ | [
21
+ /* id */ string,
22
+ /* chunks id/filename pairs, double indexed */ Array<string>,
23
+ /* name */ string,
24
+ /* async */ 1,
25
+ ]
26
+ | [
27
+ /* id */ string,
28
+ /* chunks id/filename pairs, double indexed */ Array<string>,
29
+ /* name */ string,
30
+ ];
31
+
32
+export const ID = 0;
33
+export const CHUNKS = 1;
34
+export const NAME = 2;
35
+// export const ASYNC = 3;
36
+
37
+// This logic is correct because currently only include the 4th tuple member
38
+// when the module is async. If that changes we will need to actually assert
39
+// the value is true. We don't index into the 4th slot because flow does not
40
+// like the potential out of bounds access
41
+export function isAsyncImport(metadata: ImportMetadata): boolean {
42
+ return metadata.length === 4;
43
+}
packages/react/src/__tests__/ReactFetch-test.js
+9
-1
@@ -46,9 +46,17 @@ describe('ReactFetch', () => {
46
if (gate(flags => !flags.www)) {
47
jest.mock('react', () => require('react/react.shared-subset'));
48
}
49
+ jest.mock('react-server-dom-webpack/server', () =>
50
+ require('react-server-dom-webpack/server.browser'),
51
+ );
52
+ require('react-server-dom-webpack/src/__tests__/utils/WebpackMock');
53
54
React = require('react');
51
- ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
55
+ ReactServerDOMServer = require('react-server-dom-webpack/server');
56
+
57
+ jest.resetModules();
58
+ jest.unmock('react');
59
+ jest.unmock('react-server-dom-webpack/server');
60
ReactServerDOMClient = require('react-server-dom-webpack/client');
61
use = React.use;
62
cache = React.cache;
packages/react/src/__tests__/ReactFetchEdge-test.js
+9
-1
@@ -51,9 +51,17 @@ describe('ReactFetch', () => {
51
if (gate(flags => !flags.www)) {
52
jest.mock('react', () => require('react/react.shared-subset'));
53
}
54
+ jest.mock('react-server-dom-webpack/server', () =>
55
+ require('react-server-dom-webpack/server.edge'),
56
+ );
57
+ require('react-server-dom-webpack/src/__tests__/utils/WebpackMock');
58
59
React = require('react');
56
- ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
60
+ ReactServerDOMServer = require('react-server-dom-webpack/server');
61
+
62
+ jest.resetModules();
63
+ jest.unmock('react');
64
+ jest.unmock('react-server-dom-webpack/server');
65
ReactServerDOMClient = require('react-server-dom-webpack/client');
66
use = React.use;
67
});
scripts/flow/environment.js
+3
-1
@@ -74,7 +74,9 @@ declare module 'EventListener' {
74
}
75
76
declare function __webpack_chunk_load__(id: string): Promise<mixed>;
77
-declare function __webpack_require__(id: string): any;
77
+declare var __webpack_require__: ((id: string) => any) & {
78
+ u: string => string,
79
+};
80
81
declare module 'fs/promises' {
82
declare var access: (path: string, mode?: number) => Promise<void>;
scripts/jest/setupHostConfigs.js
+3
@@ -125,6 +125,9 @@ function mockAllConfigs(rendererInfo) {
125
fs.statSync(nodePath.join(process.cwd(), 'packages', candidate));
126
return jest.requireActual(candidate);
127
} catch (error) {
128
+ if (error.code !== 'ENOENT') {
129
+ throw error;
130
+ }
131
// try without a part
132
}
133
parts.pop();
scripts/shared/inlinedHostConfigs.js
+13
-2
@@ -37,6 +37,8 @@ module.exports = [
37
'react-server-dom-webpack/server',
38
'react-server-dom-webpack/server.node.unbundled',
39
'react-server-dom-webpack/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node
40
+ 'react-server-dom-webpack/src/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
41
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerNode.js',
42
'react-devtools',
43
'react-devtools-core',
44
'react-devtools-shell',
@@ -90,6 +92,8 @@ module.exports = [
92
'react-server-dom-webpack/server.browser',
93
'react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js', // react-server-dom-webpack/client.browser
94
'react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js', // react-server-dom-webpack/server.browser
95
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js',
96
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser.js',
97
'react-devtools',
98
'react-devtools-core',
99
'react-devtools-shell',
@@ -112,6 +116,7 @@ module.exports = [
116
'react-server-dom-esm',
117
'react-server-dom-esm/client',
118
'react-server-dom-esm/client.browser',
119
+ 'react-server-dom-esm/src/ReactFlightDOMClientBrowser.js', // react-server-dom-esm/client.browser
120
'react-devtools',
121
'react-devtools-core',
122
'react-devtools-shell',
@@ -146,6 +151,8 @@ module.exports = [
151
'react-server-dom-webpack/server.edge',
152
'react-server-dom-webpack/src/ReactFlightDOMClientEdge.js', // react-server-dom-webpack/client.edge
153
'react-server-dom-webpack/src/ReactFlightDOMServerEdge.js', // react-server-dom-webpack/server.edge
154
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js',
155
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer.js',
156
'react-devtools',
157
'react-devtools-core',
158
'react-devtools-shell',
@@ -178,6 +185,9 @@ module.exports = [
185
'react-server-dom-webpack/server',
186
'react-server-dom-webpack/server.node',
187
'react-server-dom-webpack/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node
188
+ 'react-server-dom-webpack/src/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
189
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js',
190
+ 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer.js',
191
'react-server-dom-webpack/node-register',
192
'react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js',
193
'react-devtools',
@@ -212,7 +222,8 @@ module.exports = [
222
'react-server-dom-esm/client.node',
223
'react-server-dom-esm/server',
224
'react-server-dom-esm/server.node',
215
- 'react-server-dom-esm/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node
225
+ 'react-server-dom-esm/src/ReactFlightDOMServerNode.js', // react-server-dom-esm/server.node
226
+ 'react-server-dom-esm/src/ReactFlightDOMClientNode.js', // react-server-dom-esm/client.node
227
'react-devtools',
228
'react-devtools-core',
229
'react-devtools-shell',
@@ -234,7 +245,7 @@ module.exports = [
245
'react-dom/src/ReactDOMSharedSubset.js',
246
'react-dom-bindings',
247
'react-server-dom-webpack',
237
- 'react-dom/src/server/ReactDOMLegacyServerImpl.js', // not an entrypoint, but only usable in *Brower and *Node files
248
+ 'react-dom/src/server/ReactDOMLegacyServerImpl.js', // not an entrypoint, but only usable in *Browser and *Node files
249
'react-dom/src/server/ReactDOMLegacyServerBrowser.js', // react-dom/server.browser
250
'react-dom/src/server/ReactDOMLegacyServerNode.js', // react-dom/server.node
251
'react-dom/src/server/ReactDOMLegacyServerNode.classic.fb.js',