Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight] Preinitialize Imports #26795

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ module.exports = {
'packages/react-refresh/**/*.js',
'packages/react-server-dom-esm/**/*.js',
'packages/react-server-dom-webpack/**/*.js',
'packages/react-server-dom-turbopack/**/*.js',
'packages/react-test-renderer/**/*.js',
'packages/react-debug-tools/**/*.js',
'packages/react-devtools-extensions/**/*.js',
Expand Down Expand Up @@ -415,9 +416,7 @@ module.exports = {
},
},
{
files: [
'packages/react-native-renderer/**/*.js',
],
files: ['packages/react-native-renderer/**/*.js'],
globals: {
nativeFabricUIManager: 'readonly',
},
Expand All @@ -426,7 +425,14 @@ module.exports = {
files: ['packages/react-server-dom-webpack/**/*.js'],
globals: {
__webpack_chunk_load__: 'readonly',
__webpack_require__: 'readonly',
__webpack_require__: true,
},
},
{
files: ['packages/react-server-dom-turbopack/**/*.js'],
globals: {
__turbopack_load__: 'readonly',
__turbopack_require__: 'readonly',
},
},
{
Expand Down
1 change: 1 addition & 0 deletions fixtures/flight-esm/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18
38 changes: 28 additions & 10 deletions fixtures/flight-esm/server/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const compress = require('compression');
const chalk = require('chalk');
const express = require('express');
const http = require('http');
const React = require('react');

const {renderToPipeableStream} = require('react-dom/server');
const {createFromNodeStream} = require('react-server-dom-esm/client');
Expand Down Expand Up @@ -62,23 +63,39 @@ app.all('/', async function (req, res, next) {
if (req.accepts('text/html')) {
try {
const rscResponse = await promiseForData;

const moduleBaseURL = '/src';

// For HTML, we're a "client" emulator that runs the client code,
// so we start by consuming the RSC payload. This needs the local file path
// to load the source files from as well as the URL path for preloads.
const root = await createFromNodeStream(
rscResponse,
moduleBasePath,
moduleBaseURL
);

let root;
let Root = () => {
if (root) {
return React.use(root);
}

return React.use(
(root = createFromNodeStream(
rscResponse,
moduleBasePath,
moduleBaseURL
))
);
};
// Render it into HTML by resolving the client components
res.set('Content-type', 'text/html');
const {pipe} = renderToPipeableStream(root, {
// TODO: bootstrapModules inserts a preload before the importmap which causes
// the import map to be invalid. We need to fix that in Float somehow.
// bootstrapModules: ['/src/index.js'],
const {pipe} = renderToPipeableStream(React.createElement(Root), {
importMap: {
imports: {
react: 'https://esm.sh/react@experimental?pin=v124&dev',
'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
'react-server-dom-esm/client':
'/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
},
},
bootstrapModules: ['/src/index.js'],
});
pipe(res);
} catch (e) {
Expand All @@ -89,6 +106,7 @@ app.all('/', async function (req, res, next) {
} else {
try {
const rscResponse = await promiseForData;

// For other request, we pass-through the RSC payload.
res.set('Content-type', 'text/x-component');
rscResponse.on('data', data => {
Expand Down
3 changes: 3 additions & 0 deletions fixtures/flight-esm/server/region.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ async function renderApp(res, returnValue) {
// For client-invoked server actions we refresh the tree and return a return value.
const payload = returnValue ? {returnValue, root} : root;
const {pipe} = renderToPipeableStream(payload, moduleBasePath);
await new Promise(res => {
setTimeout(res, 1000);
});
pipe(res);
}

Expand Down
20 changes: 1 addition & 19 deletions fixtures/flight-esm/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@ import {getServerState} from './ServerState.js';

const h = React.createElement;

const importMap = {
imports: {
react: 'https://esm.sh/react@experimental?pin=v124&dev',
'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
'react-server-dom-esm/client':
'/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
},
};

export default async function App() {
const res = await fetch('http://localhost:3001/todos');
const todos = await res.json();
Expand All @@ -42,12 +32,6 @@ export default async function App() {
rel: 'stylesheet',
href: '/src/style.css',
precedence: 'default',
}),
h('script', {
type: 'importmap',
dangerouslySetInnerHTML: {
__html: JSON.stringify(importMap),
},
})
),
h(
Expand Down Expand Up @@ -84,9 +68,7 @@ export default async function App() {
'Like'
)
)
),
// TODO: Move this to bootstrapModules.
h('script', {type: 'module', src: '/src/index.js'})
)
)
);
}
1 change: 1 addition & 0 deletions fixtures/flight/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18
6 changes: 6 additions & 0 deletions fixtures/flight/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ module.exports = function (webpackEnv) {
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
fs.existsSync(f)
),
react: [
'react/',
'react-dom/',
'react-server-dom-webpack/',
'scheduler/',
],
},
},
infrastructureLogging: {
Expand Down
28 changes: 24 additions & 4 deletions fixtures/flight/server/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const compress = require('compression');
const chalk = require('chalk');
const express = require('express');
const http = require('http');
const React = require('react');

const {renderToPipeableStream} = require('react-dom/server');
const {createFromNodeStream} = require('react-server-dom-webpack/client');
Expand Down Expand Up @@ -62,6 +63,11 @@ if (process.env.NODE_ENV === 'development') {
webpackMiddleware(compiler, {
publicPath: paths.publicUrlOrPath.slice(0, -1),
serverSideRender: true,
headers: () => {
return {
'Cache-Control': 'no-store, must-revalidate',
};
},
})
);
app.use(webpackHotMiddleware(compiler));
Expand Down Expand Up @@ -121,9 +127,9 @@ app.all('/', async function (req, res, next) {
buildPath = path.join(__dirname, '../build/');
}
// Read the module map from the virtual file system.
const moduleMap = JSON.parse(
const ssrBundleConfig = JSON.parse(
await virtualFs.readFile(
path.join(buildPath, 'react-ssr-manifest.json'),
path.join(buildPath, 'react-ssr-bundle-config.json'),
'utf8'
)
);
Expand All @@ -138,10 +144,24 @@ app.all('/', async function (req, res, next) {
// For HTML, we're a "client" emulator that runs the client code,
// so we start by consuming the RSC payload. This needs a module
// map that reverse engineers the client-side path to the SSR path.
const root = await createFromNodeStream(rscResponse, moduleMap);
let root;
let Root = () => {
if (root) {
return React.use(root);
}
return React.use(
(root = createFromNodeStream(
rscResponse,
ssrBundleConfig.ssrManifest,
{
moduleLoading: ssrBundleConfig.moduleLoading,
}
))
);
};
// Render it into HTML by resolving the client components
res.set('Content-type', 'text/html');
const {pipe} = renderToPipeableStream(root, {
const {pipe} = renderToPipeableStream(React.createElement(Root), {
bootstrapScripts: mainJSChunks,
});
pipe(res);
Expand Down
3 changes: 3 additions & 0 deletions fixtures/flight/server/region.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ async function renderApp(res, returnValue) {
// For client-invoked server actions we refresh the tree and return a return value.
const payload = returnValue ? {returnValue, root} : root;
const {pipe} = renderToPipeableStream(payload, moduleMap);
await new Promise(res => {
setTimeout(res, 1000);
});
pipe(res);
}

Expand Down
7 changes: 7 additions & 0 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ClientReferenceMetadata,
SSRManifest,
StringDecoder,
ModuleLoading,
} from './ReactFlightClientConfig';

import type {HintModel} from 'react-server/src/ReactFlightServerConfig';
Expand All @@ -33,6 +34,7 @@ import {
readPartialStringChunk,
readFinalStringChunk,
createStringDecoder,
prepareDestinationForModule,
} from './ReactFlightClientConfig';

import {registerServerReference} from './ReactFlightReplyClient';
Expand Down Expand Up @@ -176,6 +178,7 @@ Chunk.prototype.then = function <T>(

export type Response = {
_bundlerConfig: SSRManifest,
_moduleLoading: ModuleLoading,
_callServer: CallServerCallback,
_chunks: Map<number, SomeChunk<any>>,
_fromJSON: (key: string, value: JSONValue) => any,
Expand Down Expand Up @@ -704,11 +707,13 @@ function missingCall() {

export function createResponse(
bundlerConfig: SSRManifest,
moduleLoading: ModuleLoading,
callServer: void | CallServerCallback,
): Response {
const chunks: Map<number, SomeChunk<any>> = new Map();
const response: Response = {
_bundlerConfig: bundlerConfig,
_moduleLoading: moduleLoading,
_callServer: callServer !== undefined ? callServer : missingCall,
_chunks: chunks,
_stringDecoder: createStringDecoder(),
Expand Down Expand Up @@ -771,6 +776,8 @@ function resolveModule(
clientReferenceMetadata,
);

prepareDestinationForModule(response._moduleLoading, clientReferenceMetadata);

// TODO: Add an option to encode modules that are lazy loaded.
// For now we preload all modules as early as possible since it's likely
// that we'll need them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

declare var $$$config: any;

export opaque type ModuleLoading = mixed;
export opaque type SSRManifest = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
Expand All @@ -35,6 +36,8 @@ export const resolveServerReference = $$$config.resolveServerReference;
export const preloadModule = $$$config.preloadModule;
export const requireModule = $$$config.requireModule;
export const dispatchHint = $$$config.dispatchHint;
export const prepareDestinationForModule =
$$$config.prepareDestinationForModule;
export const usedWithSSR = true;

export opaque type Source = mixed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigESMBundler';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = false;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackBrowser';
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = false;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = false;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export type Response = any;
export opaque type ModuleLoading = mixed;
export opaque type SSRManifest = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
Expand All @@ -20,4 +21,5 @@ export const resolveClientReference: any = null;
export const resolveServerReference: any = null;
export const preloadModule: any = null;
export const requireModule: any = null;
export const prepareDestinationForModule: any = null;
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopack';
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigBundlerTurbopackServer';
export * from 'react-server-dom-turbopack/src/ReactFlightClientConfigTargetTurbopackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export type Response = any;
export opaque type ModuleLoading = mixed;
export opaque type SSRManifest = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
export opaque type ClientReferenceMetadata = mixed;
export opaque type ClientReference<T> = mixed; // eslint-disable-line no-unused-vars
export const resolveClientReference: any = null;
export const resolveServerReference: any = null;
export const preloadModule: any = null;
export const requireModule: any = null;
export const prepareDestinationForModule: any = null;
export const usedWithSSR = true;
Loading