Skip to content

Commit 19237fb

Browse files
committed
ESM implementation of module preinitialization
1 parent cc7fdfd commit 19237fb

15 files changed

+153
-49
lines changed

fixtures/flight-esm/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v18

fixtures/flight-esm/server/global.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const compress = require('compression');
1010
const chalk = require('chalk');
1111
const express = require('express');
1212
const http = require('http');
13+
const React = require('react');
1314

1415
const {renderToPipeableStream} = require('react-dom/server');
1516
const {createFromNodeStream} = require('react-server-dom-esm/client');
@@ -62,23 +63,39 @@ app.all('/', async function (req, res, next) {
6263
if (req.accepts('text/html')) {
6364
try {
6465
const rscResponse = await promiseForData;
65-
6666
const moduleBaseURL = '/src';
6767

6868
// For HTML, we're a "client" emulator that runs the client code,
6969
// so we start by consuming the RSC payload. This needs the local file path
7070
// 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+
};
7686
// Render it into HTML by resolving the client components
7787
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'],
8299
});
83100
pipe(res);
84101
} catch (e) {
@@ -89,6 +106,7 @@ app.all('/', async function (req, res, next) {
89106
} else {
90107
try {
91108
const rscResponse = await promiseForData;
109+
92110
// For other request, we pass-through the RSC payload.
93111
res.set('Content-type', 'text/x-component');
94112
rscResponse.on('data', data => {

fixtures/flight-esm/src/App.js

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,6 @@ import {getServerState} from './ServerState.js';
99

1010
const h = React.createElement;
1111

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-
2212
export default async function App() {
2313
const res = await fetch('http://localhost:3001/todos');
2414
const todos = await res.json();
@@ -42,12 +32,6 @@ export default async function App() {
4232
rel: 'stylesheet',
4333
href: '/src/style.css',
4434
precedence: 'default',
45-
}),
46-
h('script', {
47-
type: 'importmap',
48-
dangerouslySetInnerHTML: {
49-
__html: JSON.stringify(importMap),
50-
},
5135
})
5236
),
5337
h(
@@ -84,9 +68,7 @@ export default async function App() {
8468
'Like'
8569
)
8670
)
87-
),
88-
// TODO: Move this to bootstrapModules.
89-
h('script', {type: 'module', src: '/src/index.js'})
71+
)
9072
)
9173
);
9274
}

fixtures/flight-esm/yarn.lock

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -540,17 +540,17 @@ raw-body@2.5.2:
540540
unpipe "1.0.0"
541541

542542
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==
546546
dependencies:
547547
loose-envify "^1.1.0"
548-
scheduler "0.0.0-experimental-018c58c9c-20230601"
548+
scheduler "0.0.0-experimental-b9be4537c-20230905"
549549

550550
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==
554554
dependencies:
555555
loose-envify "^1.1.0"
556556

@@ -588,10 +588,10 @@ safe-buffer@5.1.2:
588588
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
589589
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
590590

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==
595595
dependencies:
596596
loose-envify "^1.1.0"
597597

fixtures/flight/server/global.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ app.all('/', async function (req, res, next) {
148148
let root;
149149
let Root = () => {
150150
if (root) {
151-
return root;
151+
return React.use(root);
152152
}
153153
return React.use(
154154
(root = createFromNodeStream(rscResponse, ssrManifest))

fixtures/flight/server/region.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ async function renderApp(res, returnValue) {
9595
// For client-invoked server actions we refresh the tree and return a return value.
9696
const payload = returnValue ? {returnValue, root} : root;
9797
const {pipe} = renderToPipeableStream(payload, moduleMap);
98+
await new Promise(res => {
99+
setTimeout(res, 1000);
100+
});
98101
pipe(res);
99102
}
100103

packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
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';
1213
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1314
export const usedWithSSR = false;

packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
* @flow
88
*/
99

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';
1213
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1314
export const usedWithSSR = true;

packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ function refineModel<T>(code: T, model: HintModel<any>): HintModel<T> {
113113
return model;
114114
}
115115

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+
116130
export function preinitScriptForSSR(
117131
href: string,
118132
nonce: ?string,

packages/react-server-dom-esm/src/ReactFlightClientConfigESMBundler.js renamed to packages/react-server-dom-esm/src/ReactFlightClientConfigBundlerESM.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import type {
1212
FulfilledThenable,
1313
RejectedThenable,
1414
} from 'shared/ReactTypes';
15+
import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
1516

16-
export type SSRManifest = string; // Module root path
17+
export type SSRModuleMap = string; // Module root path
1718

1819
export type ServerManifest = string; // Module root path
1920

2021
export type ServerReferenceId = string;
2122

23+
import {prepareDestinationForModuleImpl} from 'react-client/src/ReactFlightClientConfig';
24+
2225
export opaque type ClientReferenceMetadata = [
2326
string, // module path
2427
string, // export name
@@ -30,8 +33,22 @@ export opaque type ClientReference<T> = {
3033
name: string,
3134
};
3235

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+
3350
export function resolveClientReference<T>(
34-
bundlerConfig: SSRManifest,
51+
bundlerConfig: SSRModuleMap,
3552
metadata: ClientReferenceMetadata,
3653
): ClientReference<T> {
3754
const baseURL = bundlerConfig;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export type Options = {
3636
function createResponseFromOptions(options: void | Options) {
3737
return createResponse(
3838
options && options.moduleBaseURL ? options.moduleBaseURL : '',
39+
null,
3940
options && options.callServer ? options.callServer : undefined,
41+
undefined, // nonce
4042
);
4143
}
4244

packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,22 @@ export function createServerReference<A: Iterable<any>, T>(
3838
return createServerReferenceImpl(id, noServerCall);
3939
}
4040

41+
export type Options = {
42+
nonce?: string,
43+
};
44+
4145
function createFromNodeStream<T>(
4246
stream: Readable,
4347
moduleRootPath: string,
44-
moduleBaseURL: string, // TODO: Used for preloading hints
48+
moduleBaseURL: string,
49+
options?: Options,
4550
): 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+
);
4757
stream.on('data', chunk => {
4858
processBinaryChunk(response, chunk);
4959
});

scripts/shared/inlinedHostConfigs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ module.exports = [
112112
'react-server-dom-esm',
113113
'react-server-dom-esm/client',
114114
'react-server-dom-esm/client.browser',
115+
'react-server-dom-esm/src/ReactFlightDOMClientBrowser.js', // react-server-dom-esm/client.browser
115116
'react-devtools',
116117
'react-devtools-core',
117118
'react-devtools-shell',
@@ -214,7 +215,8 @@ module.exports = [
214215
'react-server-dom-esm/client.node',
215216
'react-server-dom-esm/server',
216217
'react-server-dom-esm/server.node',
217-
'react-server-dom-esm/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node
218+
'react-server-dom-esm/src/ReactFlightDOMServerNode.js', // react-server-dom-esm/server.node
219+
'react-server-dom-esm/src/ReactFlightDOMClientNode.js', // react-server-dom-esm/client.node
218220
'react-devtools',
219221
'react-devtools-core',
220222
'react-devtools-shell',

0 commit comments

Comments
 (0)