Skip to content

Commit 4cc6990

Browse files
committedSep 5, 2023
ESM implementation of module preinitialization
1 parent e64e4a5 commit 4cc6990

15 files changed

+128
-41
lines changed
 

‎fixtures/flight-esm/.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v18

‎fixtures/flight-esm/server/global.js

+28-10
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

+1-19
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/server/global.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ app.all('/', async function (req, res, next) {
147147
let root;
148148
let Root = () => {
149149
if (root) {
150-
return root;
150+
return React.use(root);
151151
}
152152
return React.use(
153153
(root = createFromNodeStream(

‎fixtures/flight/server/region.js

+3
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

+2-1
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

+3-2
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

+12-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,18 @@ export function dispatchHint(code: string, model: HintModel): void {
6464
}
6565
}
6666

67-
export function preinitModulesForSSR(href: string, crossOrigin: ?string) {
67+
export function preinitModuleForSSR(href: string, crossOrigin: ?string) {
68+
const dispatcher = ReactDOMCurrentDispatcher.current;
69+
if (dispatcher) {
70+
if (typeof crossOrigin === 'string') {
71+
dispatcher.preinitModule(href, {crossOrigin});
72+
} else {
73+
dispatcher.preinitModule(href);
74+
}
75+
}
76+
}
77+
78+
export function preinitScriptForSSR(href: string, crossOrigin: ?string) {
6879
const dispatcher = ReactDOMCurrentDispatcher.current;
6980
if (dispatcher) {
7081
if (typeof crossOrigin === 'string') {

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

+18-2
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,21 @@ 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+
metadata: ClientReferenceMetadata,
45+
) {
46+
prepareDestinationForModuleImpl(moduleLoading, metadata[0]);
47+
}
48+
3349
export function resolveClientReference<T>(
34-
bundlerConfig: SSRManifest,
50+
bundlerConfig: SSRModuleMap,
3551
metadata: ClientReferenceMetadata,
3652
): ClientReference<T> {
3753
const baseURL = bundlerConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
) {
16+
// In the browser we don't need to prepare our destination since the browser is the Destination
17+
}
Original file line numberDiff line numberDiff line change
@@ -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+
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+
) {
25+
if (typeof moduleLoading === 'string') {
26+
preinitModuleForSSR(moduleLoading + mod, undefined);
27+
} else if (moduleLoading !== null) {
28+
preinitModuleForSSR(moduleLoading.prefix + mod, moduleLoading.crossOrigin);
29+
}
30+
}

‎packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ 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,
4041
);
4142
}

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ export function createServerReference<A: Iterable<any>, T>(
4141
function createFromNodeStream<T>(
4242
stream: Readable,
4343
moduleRootPath: string,
44-
moduleBaseURL: string, // TODO: Used for preloading hints
44+
moduleBaseURL: string,
4545
): Thenable<T> {
46-
const response: Response = createResponse(moduleRootPath, noServerCall);
46+
const response: Response = createResponse(
47+
moduleRootPath,
48+
moduleBaseURL,
49+
noServerCall,
50+
);
4751
stream.on('data', chunk => {
4852
processBinaryChunk(response, chunk);
4953
});

‎packages/react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import {preinitModulesForSSR} from 'react-client/src/ReactFlightClientConfig';
10+
import {preinitScriptForSSR} from 'react-client/src/ReactFlightClientConfig';
1111

1212
export type ModuleLoading = null | {
1313
prefix: string,
@@ -21,7 +21,7 @@ export function prepareDestinationWithChunks(
2121
) {
2222
if (moduleLoading !== null) {
2323
for (let i = 1; i < chunks.length; i += 2) {
24-
preinitModulesForSSR(
24+
preinitScriptForSSR(
2525
moduleLoading.prefix + chunks[i],
2626
moduleLoading.crossOrigin,
2727
);

‎scripts/shared/inlinedHostConfigs.js

+3-1
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)