Skip to content

Commit 19237fb

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

File tree

15 files changed

+153
-49
lines changed

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 @@ [email protected]:
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 @@ [email protected]:
588588
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
589589
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
590590

591-
[email protected]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+
[email protected]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;

0 commit comments

Comments
 (0)