From 934ce8a753706b45863a108ec21a38dbc77ed106 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 27 Jan 2024 21:36:53 -0500 Subject: [PATCH 01/22] Setup react-server-dom-parcel package --- .eslintrc.js | 8 + ReactVersions.js | 1 + ...ctFlightClientConfig.dom-browser-parcel.js | 15 ++ ...ReactFlightClientConfig.dom-edge-parcel.js | 15 ++ ...ReactFlightClientConfig.dom-node-parcel.js | 15 ++ .../src/shared/ReactFlightClientConfigDOM.js | 33 +++ packages/react-server-dom-parcel/README.md | 5 + .../react-server-dom-parcel/client.browser.js | 10 + .../react-server-dom-parcel/client.edge.js | 10 + packages/react-server-dom-parcel/client.js | 10 + .../react-server-dom-parcel/client.node.js | 10 + packages/react-server-dom-parcel/index.js | 10 + .../npm/client.browser.js | 7 + .../npm/client.edge.js | 7 + .../react-server-dom-parcel/npm/client.js | 3 + .../npm/client.node.js | 7 + packages/react-server-dom-parcel/npm/index.js | 12 + .../npm/server.browser.js | 7 + .../npm/server.edge.js | 7 + .../react-server-dom-parcel/npm/server.js | 6 + .../npm/server.node.js | 7 + packages/react-server-dom-parcel/package.json | 69 ++++++ .../react-server-dom-parcel/server.browser.js | 10 + .../react-server-dom-parcel/server.edge.js | 10 + packages/react-server-dom-parcel/server.js | 13 + .../react-server-dom-parcel/server.node.js | 10 + .../src/ReactFlightClientConfigBundlerNode.js | 156 ++++++++++++ .../ReactFlightClientConfigBundlerParcel.js | 227 ++++++++++++++++++ ...tFlightClientConfigBundlerParcelBrowser.js | 18 ++ ...ctFlightClientConfigBundlerParcelServer.js | 16 ++ ...ctFlightClientConfigTargetParcelBrowser.js | 18 ++ ...actFlightClientConfigTargetParcelServer.js | 44 ++++ .../src/ReactFlightDOMClientBrowser.js | 111 +++++++++ .../src/ReactFlightDOMClientEdge.js | 130 ++++++++++ .../src/ReactFlightDOMClientNode.js | 76 ++++++ .../src/ReactFlightDOMServerBrowser.js | 102 ++++++++ .../src/ReactFlightDOMServerEdge.js | 102 ++++++++ .../src/ReactFlightDOMServerNode.js | 189 +++++++++++++++ .../src/ReactFlightParcelReferences.js | 34 +++ .../ReactFlightServerConfigParcelBundler.js | 93 +++++++ .../src/shared/ReactFlightImportMetadata.js | 43 ++++ ...ctFlightServerConfig.dom-browser-parcel.js | 18 ++ ...ReactFlightServerConfig.dom-edge-parcel.js | 33 +++ ...ReactFlightServerConfig.dom-node-parcel.js | 22 ++ scripts/flow/environment.js | 3 + scripts/rollup/bundles.js | 58 +++++ scripts/rollup/validate/eslintrc.cjs.js | 4 + scripts/rollup/validate/eslintrc.cjs2015.js | 4 + scripts/rollup/validate/eslintrc.esm.js | 4 + scripts/rollup/validate/eslintrc.umd.js | 4 + scripts/shared/inlinedHostConfigs.js | 99 ++++++++ 51 files changed, 1925 insertions(+) create mode 100644 packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js create mode 100644 packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js create mode 100644 packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js create mode 100644 packages/react-server-dom-parcel/README.md create mode 100644 packages/react-server-dom-parcel/client.browser.js create mode 100644 packages/react-server-dom-parcel/client.edge.js create mode 100644 packages/react-server-dom-parcel/client.js create mode 100644 packages/react-server-dom-parcel/client.node.js create mode 100644 packages/react-server-dom-parcel/index.js create mode 100644 packages/react-server-dom-parcel/npm/client.browser.js create mode 100644 packages/react-server-dom-parcel/npm/client.edge.js create mode 100644 packages/react-server-dom-parcel/npm/client.js create mode 100644 packages/react-server-dom-parcel/npm/client.node.js create mode 100644 packages/react-server-dom-parcel/npm/index.js create mode 100644 packages/react-server-dom-parcel/npm/server.browser.js create mode 100644 packages/react-server-dom-parcel/npm/server.edge.js create mode 100644 packages/react-server-dom-parcel/npm/server.js create mode 100644 packages/react-server-dom-parcel/npm/server.node.js create mode 100644 packages/react-server-dom-parcel/package.json create mode 100644 packages/react-server-dom-parcel/server.browser.js create mode 100644 packages/react-server-dom-parcel/server.edge.js create mode 100644 packages/react-server-dom-parcel/server.js create mode 100644 packages/react-server-dom-parcel/server.node.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightDOMClientBrowser.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightDOMClientEdge.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightDOMClientNode.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightDOMServerBrowser.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightDOMServerEdge.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightDOMServerNode.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js create mode 100644 packages/react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler.js create mode 100644 packages/react-server-dom-parcel/src/shared/ReactFlightImportMetadata.js create mode 100644 packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js create mode 100644 packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js create mode 100644 packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js diff --git a/.eslintrc.js b/.eslintrc.js index eaad9393c5685..f63deeab0243b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -327,6 +327,7 @@ module.exports = { 'packages/react-server-dom-esm/**/*.js', 'packages/react-server-dom-webpack/**/*.js', 'packages/react-server-dom-turbopack/**/*.js', + 'packages/react-server-dom-parcel/**/*.js', 'packages/react-server-dom-fb/**/*.js', 'packages/react-test-renderer/**/*.js', 'packages/react-debug-tools/**/*.js', @@ -436,6 +437,13 @@ module.exports = { __turbopack_require__: 'readonly', }, }, + { + files: ['packages/react-server-dom-parcel/**/*.js'], + globals: { + parcelRequire: 'readonly', + __parcel__import__: 'readonly', + }, + }, { files: ['packages/scheduler/**/*.js'], globals: { diff --git a/ReactVersions.js b/ReactVersions.js index ef97ee7e3afc3..c4235dce9e01c 100644 --- a/ReactVersions.js +++ b/ReactVersions.js @@ -36,6 +36,7 @@ const stablePackages = { 'react-dom': ReactVersion, 'react-server-dom-webpack': ReactVersion, 'react-server-dom-turbopack': ReactVersion, + 'react-server-dom-parcel': ReactVersion, 'react-is': ReactVersion, 'react-reconciler': '0.30.0', 'react-refresh': '0.15.0', diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js new file mode 100644 index 0000000000000..0dc2dd30620b1 --- /dev/null +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js @@ -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-parcel/src/ReactFlightClientConfigBundlerParcel'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser'; +export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; +export const usedWithSSR = false; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js new file mode 100644 index 0000000000000..ec09b570a841c --- /dev/null +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js @@ -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-parcel/src/ReactFlightClientConfigBundlerParcel'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer'; +export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; +export const usedWithSSR = true; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js new file mode 100644 index 0000000000000..8bb2393b2c5e6 --- /dev/null +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js @@ -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/ReactFlightClientConfigNode'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer'; +export * from 'react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer'; +export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; +export const usedWithSSR = true; diff --git a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js index 18ef25b06842e..9b0582cde8c3e 100644 --- a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js +++ b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js @@ -11,6 +11,7 @@ // It is the configuration of the FlightClient behavior which can run in either environment. import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM'; +import type {PreinitStyleOptions, PreloadImplOptions, PreloadModuleImplOptions} from 'react-dom/src/shared/ReactDOMTypes'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; @@ -140,3 +141,35 @@ export function preinitScriptForSSR( }); } } + +export function preloadForSSR( + href: string, + as: string, + options: ?PreloadImplOptions +) { + const dispatcher = ReactDOMCurrentDispatcher.current; + if (dispatcher) { + dispatcher.preload(href, as, options); + } +} + +export function preloadModuleForSSR( + href: string, + options: ?PreloadModuleImplOptions +) { + const dispatcher = ReactDOMCurrentDispatcher.current; + if (dispatcher) { + dispatcher.preloadModule(href, options); + } +} + +export function preinitStyleForSSR( + href: string, + precedence: ?string, + options?: ?PreinitStyleOptions, +) { + const dispatcher = ReactDOMCurrentDispatcher.current; + if (dispatcher) { + dispatcher.preinitStyle(href, precedence, options); + } +} diff --git a/packages/react-server-dom-parcel/README.md b/packages/react-server-dom-parcel/README.md new file mode 100644 index 0000000000000..8cbf767d39fe8 --- /dev/null +++ b/packages/react-server-dom-parcel/README.md @@ -0,0 +1,5 @@ +# react-server-dom-parcel + +Experimental React Flight bindings for DOM using Parcel. + +**Use it at your own risk.** diff --git a/packages/react-server-dom-parcel/client.browser.js b/packages/react-server-dom-parcel/client.browser.js new file mode 100644 index 0000000000000..7d26c2771e50a --- /dev/null +++ b/packages/react-server-dom-parcel/client.browser.js @@ -0,0 +1,10 @@ +/** + * 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 './src/ReactFlightDOMClientBrowser'; diff --git a/packages/react-server-dom-parcel/client.edge.js b/packages/react-server-dom-parcel/client.edge.js new file mode 100644 index 0000000000000..fadceeaf8443a --- /dev/null +++ b/packages/react-server-dom-parcel/client.edge.js @@ -0,0 +1,10 @@ +/** + * 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 './src/ReactFlightDOMClientEdge'; diff --git a/packages/react-server-dom-parcel/client.js b/packages/react-server-dom-parcel/client.js new file mode 100644 index 0000000000000..2dad5bb513872 --- /dev/null +++ b/packages/react-server-dom-parcel/client.js @@ -0,0 +1,10 @@ +/** + * 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 './client.browser'; diff --git a/packages/react-server-dom-parcel/client.node.js b/packages/react-server-dom-parcel/client.node.js new file mode 100644 index 0000000000000..4f435353a20f0 --- /dev/null +++ b/packages/react-server-dom-parcel/client.node.js @@ -0,0 +1,10 @@ +/** + * 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 './src/ReactFlightDOMClientNode'; diff --git a/packages/react-server-dom-parcel/index.js b/packages/react-server-dom-parcel/index.js new file mode 100644 index 0000000000000..3ce9059540c06 --- /dev/null +++ b/packages/react-server-dom-parcel/index.js @@ -0,0 +1,10 @@ +/** + * 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 + */ + +throw new Error('Use react-server-dom-parcel/client instead.'); diff --git a/packages/react-server-dom-parcel/npm/client.browser.js b/packages/react-server-dom-parcel/npm/client.browser.js new file mode 100644 index 0000000000000..fc94bc67c7cc0 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/client.browser.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-parcel-client.browser.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-parcel-client.browser.development.js'); +} diff --git a/packages/react-server-dom-parcel/npm/client.edge.js b/packages/react-server-dom-parcel/npm/client.edge.js new file mode 100644 index 0000000000000..c1a7f6ec901a3 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/client.edge.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-parcel-client.edge.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-parcel-client.edge.development.js'); +} diff --git a/packages/react-server-dom-parcel/npm/client.js b/packages/react-server-dom-parcel/npm/client.js new file mode 100644 index 0000000000000..89d93a7a7920f --- /dev/null +++ b/packages/react-server-dom-parcel/npm/client.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./client.browser'); diff --git a/packages/react-server-dom-parcel/npm/client.node.js b/packages/react-server-dom-parcel/npm/client.node.js new file mode 100644 index 0000000000000..30917bc22e1b1 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/client.node.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-parcel-client.node.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-parcel-client.node.development.js'); +} diff --git a/packages/react-server-dom-parcel/npm/index.js b/packages/react-server-dom-parcel/npm/index.js new file mode 100644 index 0000000000000..ef54db443ffa2 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/index.js @@ -0,0 +1,12 @@ +/** + * 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 + */ + +'use strict'; + +throw new Error('Use react-server-dom-parcel/client instead.'); diff --git a/packages/react-server-dom-parcel/npm/server.browser.js b/packages/react-server-dom-parcel/npm/server.browser.js new file mode 100644 index 0000000000000..c4e0d8978faa2 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/server.browser.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-parcel-server.browser.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-parcel-server.browser.development.js'); +} diff --git a/packages/react-server-dom-parcel/npm/server.edge.js b/packages/react-server-dom-parcel/npm/server.edge.js new file mode 100644 index 0000000000000..d0fa1d0001325 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/server.edge.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-parcel-server.edge.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-parcel-server.edge.development.js'); +} diff --git a/packages/react-server-dom-parcel/npm/server.js b/packages/react-server-dom-parcel/npm/server.js new file mode 100644 index 0000000000000..13a632e641179 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/server.js @@ -0,0 +1,6 @@ +'use strict'; + +throw new Error( + 'The React Server Writer cannot be used outside a react-server environment. ' + + 'You must configure Node.js using the `--conditions react-server` flag.' +); diff --git a/packages/react-server-dom-parcel/npm/server.node.js b/packages/react-server-dom-parcel/npm/server.node.js new file mode 100644 index 0000000000000..5156978a1c1e5 --- /dev/null +++ b/packages/react-server-dom-parcel/npm/server.node.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-parcel-server.node.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-parcel-server.node.development.js'); +} diff --git a/packages/react-server-dom-parcel/package.json b/packages/react-server-dom-parcel/package.json new file mode 100644 index 0000000000000..59e20b928067e --- /dev/null +++ b/packages/react-server-dom-parcel/package.json @@ -0,0 +1,69 @@ +{ + "name": "react-server-dom-parcel", + "description": "React Server Components bindings for DOM using Parcel. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.", + "version": "18.2.0", + "keywords": [ + "react" + ], + "homepage": "https://reactjs.org/", + "bugs": "https://github.com/facebook/react/issues", + "license": "MIT", + "files": [ + "LICENSE", + "README.md", + "index.js", + "client.js", + "client.browser.js", + "client.edge.js", + "client.node.js", + "server.js", + "server.browser.js", + "server.edge.js", + "server.node.js", + "cjs/", + "umd/" + ], + "exports": { + ".": "./index.js", + "./client": { + "workerd": "./client.edge.js", + "deno": "./client.edge.js", + "worker": "./client.edge.js", + "node": "./client.node.js", + "edge-light": "./client.edge.js", + "browser": "./client.browser.js", + "default": "./client.browser.js" + }, + "./client.browser": "./client.browser.js", + "./client.edge": "./client.edge.js", + "./client.node": "./client.node.js", + "./server": { + "react-server": { + "workerd": "./server.edge.js", + "deno": "./server.browser.js", + "node": "./server.node.js", + "edge-light": "./server.edge.js", + "browser": "./server.browser.js" + }, + "default": "./server.js" + }, + "./server.browser": "./server.browser.js", + "./server.edge": "./server.edge.js", + "./server.node": "./server.node.js", + "./src/*": "./src/*.js", + "./package.json": "./package.json" + }, + "main": "index.js", + "repository": { + "type" : "git", + "url" : "https://github.com/facebook/react.git", + "directory": "packages/react-server-dom-parcel" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } +} diff --git a/packages/react-server-dom-parcel/server.browser.js b/packages/react-server-dom-parcel/server.browser.js new file mode 100644 index 0000000000000..41a9fb5c44968 --- /dev/null +++ b/packages/react-server-dom-parcel/server.browser.js @@ -0,0 +1,10 @@ +/** + * 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 './src/ReactFlightDOMServerBrowser'; diff --git a/packages/react-server-dom-parcel/server.edge.js b/packages/react-server-dom-parcel/server.edge.js new file mode 100644 index 0000000000000..98f975cb4706f --- /dev/null +++ b/packages/react-server-dom-parcel/server.edge.js @@ -0,0 +1,10 @@ +/** + * 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 './src/ReactFlightDOMServerEdge'; diff --git a/packages/react-server-dom-parcel/server.js b/packages/react-server-dom-parcel/server.js new file mode 100644 index 0000000000000..83d8b8a017ff2 --- /dev/null +++ b/packages/react-server-dom-parcel/server.js @@ -0,0 +1,13 @@ +/** + * 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 + */ + +throw new Error( + 'The React Server cannot be used outside a react-server environment. ' + + 'You must configure Node.js using the `--conditions react-server` flag.', +); diff --git a/packages/react-server-dom-parcel/server.node.js b/packages/react-server-dom-parcel/server.node.js new file mode 100644 index 0000000000000..7726b9bb929d4 --- /dev/null +++ b/packages/react-server-dom-parcel/server.node.js @@ -0,0 +1,10 @@ +/** + * 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 './src/ReactFlightDOMServerNode'; diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js new file mode 100644 index 0000000000000..f1e5c5dd579bd --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js @@ -0,0 +1,156 @@ +/** + * 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 + */ + +import type { + Thenable, + FulfilledThenable, + RejectedThenable, +} from 'shared/ReactTypes'; + +import type {ImportMetadata} from './shared/ReactFlightImportMetadata'; +import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig'; + +import { + ID, + CHUNKS, + NAME, + isAsyncImport, +} from './shared/ReactFlightImportMetadata'; +import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig'; + +export type SSRModuleMap = { + [clientId: string]: { + [clientExportName: string]: ClientReference, + }, +}; + +export type ServerManifest = void; + +export type ServerReferenceId = string; + +export opaque type ClientReferenceMetadata = ImportMetadata; + +// eslint-disable-next-line no-unused-vars +export opaque type ClientReference = { + specifier: string, + name: string, + async?: boolean, +}; + +export function prepareDestinationForModule( + moduleLoading: ModuleLoading, + nonce: ?string, + metadata: ClientReferenceMetadata, +) { + prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce); +} + +export function resolveClientReference( + bundlerConfig: SSRModuleMap, + metadata: ClientReferenceMetadata, +): ClientReference { + const moduleExports = bundlerConfig[metadata[ID]]; + let resolvedModuleData = moduleExports[metadata[NAME]]; + let name; + if (resolvedModuleData) { + // The potentially aliased name. + name = resolvedModuleData.name; + } else { + // If we don't have this specific name, we might have the full module. + resolvedModuleData = moduleExports['*']; + if (!resolvedModuleData) { + throw new Error( + 'Could not find the module "' + + metadata[ID] + + '" in the React SSR Manifest. ' + + 'This is probably a bug in the React Server Components bundler.', + ); + } + name = metadata[NAME]; + } + return { + specifier: resolvedModuleData.specifier, + name: name, + async: isAsyncImport(metadata), + }; +} + +export function resolveServerReference( + bundlerConfig: ServerManifest, + id: ServerReferenceId, +): ClientReference { + const idx = id.lastIndexOf('#'); + const specifier = id.slice(0, idx); + const name = id.slice(idx + 1); + return {specifier, name}; +} + +const asyncModuleCache: Map> = new Map(); + +export function preloadModule( + metadata: ClientReference, +): null | Thenable { + const existingPromise = asyncModuleCache.get(metadata.specifier); + if (existingPromise) { + if (existingPromise.status === 'fulfilled') { + return null; + } + return existingPromise; + } else { + // $FlowFixMe[unsupported-syntax] + let modulePromise: Promise = import(metadata.specifier); + if (metadata.async) { + // If the module is async, it must have been a CJS module. + // CJS modules are accessed through the default export in + // Node.js so we have to get the default export to get the + // full module exports. + modulePromise = modulePromise.then(function (value) { + return (value: any).default; + }); + } + modulePromise.then( + value => { + const fulfilledThenable: FulfilledThenable = + (modulePromise: any); + fulfilledThenable.status = 'fulfilled'; + fulfilledThenable.value = value; + }, + reason => { + const rejectedThenable: RejectedThenable = (modulePromise: any); + rejectedThenable.status = 'rejected'; + rejectedThenable.reason = reason; + }, + ); + asyncModuleCache.set(metadata.specifier, modulePromise); + return modulePromise; + } +} + +export function requireModule(metadata: ClientReference): T { + let moduleExports; + // We assume that preloadModule has been called before, which + // should have added something to the module cache. + const promise: any = asyncModuleCache.get(metadata.specifier); + if (promise.status === 'fulfilled') { + moduleExports = promise.value; + } else { + throw promise.reason; + } + if (metadata.name === '*') { + // This is a placeholder value that represents that the caller imported this + // as a CommonJS module as is. + return moduleExports; + } + if (metadata.name === '') { + // This is a placeholder value that represents that the caller accessed the + // default property of this if it was an ESM interop module. + return moduleExports.default; + } + return moduleExports[metadata.name]; +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js new file mode 100644 index 0000000000000..92a172b79b360 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js @@ -0,0 +1,227 @@ +/** + * 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 + */ + +import type { + Thenable, + FulfilledThenable, + RejectedThenable, +} from 'shared/ReactTypes'; + +import type { + ImportMetadata, + ImportManifestEntry, +} from './shared/ReactFlightImportMetadata'; +import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig'; + +import { + ID, + CHUNKS, + NAME, + isAsyncImport, +} from './shared/ReactFlightImportMetadata'; + +import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig'; + +import {loadChunk} from 'react-client/src/ReactFlightClientConfig'; + +export type SSRModuleMap = null | { + [clientId: string]: { + [clientExportName: string]: ClientReferenceManifestEntry, + }, +}; + +export type ServerManifest = { + [id: string]: ImportManifestEntry, +}; + +export type ServerReferenceId = string; + +export opaque type ClientReferenceManifestEntry = ImportManifestEntry; +export opaque type ClientReferenceMetadata = ImportMetadata; + +// eslint-disable-next-line no-unused-vars +export opaque type ClientReference = ClientReferenceMetadata; + +export function prepareDestinationForModule( + moduleLoading: ModuleLoading, + nonce: ?string, + metadata: ClientReferenceMetadata, +) { + prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce); +} + +export function resolveClientReference( + bundlerConfig: SSRModuleMap, + metadata: ClientReferenceMetadata, +): ClientReference { + if (bundlerConfig) { + const moduleExports = bundlerConfig[metadata[ID]]; + let resolvedModuleData = moduleExports[metadata[NAME]]; + let name; + if (resolvedModuleData) { + // The potentially aliased name. + name = resolvedModuleData.name; + } else { + // If we don't have this specific name, we might have the full module. + resolvedModuleData = moduleExports['*']; + if (!resolvedModuleData) { + throw new Error( + 'Could not find the module "' + + metadata[ID] + + '" in the React SSR Manifest. ' + + 'This is probably a bug in the React Server Components bundler.', + ); + } + name = metadata[NAME]; + } + if (isAsyncImport(metadata)) { + return [ + resolvedModuleData.id, + resolvedModuleData.chunks, + name, + 1 /* async */, + ]; + } else { + return [resolvedModuleData.id, resolvedModuleData.chunks, name]; + } + } + return metadata; +} + +export function resolveServerReference( + bundlerConfig: ServerManifest, + id: ServerReferenceId, +): ClientReference { + let name = ''; + let resolvedModuleData = bundlerConfig[id]; + if (resolvedModuleData) { + // The potentially aliased name. + name = resolvedModuleData.name; + } else { + // We didn't find this specific export name but we might have the * export + // which contains this name as well. + // TODO: It's unfortunate that we now have to parse this string. We should + // probably go back to encoding path and name separately on the client reference. + const idx = id.lastIndexOf('#'); + if (idx !== -1) { + name = id.slice(idx + 1); + resolvedModuleData = bundlerConfig[id.slice(0, idx)]; + } + if (!resolvedModuleData) { + throw new Error( + 'Could not find the module "' + + id + + '" in the React Server Manifest. ' + + 'This is probably a bug in the React Server Components bundler.', + ); + } + } + // TODO: This needs to return async: true if it's an async module. + return [resolvedModuleData.id, resolvedModuleData.chunks, name]; +} + +// The chunk cache contains all the chunks we've preloaded so far. +// If they're still pending they're a thenable. This map also exists +// in Webpack but unfortunately it's not exposed so we have to +// replicate it in user space. null means that it has already loaded. +const chunkCache: Map> = new Map(); + +function requireAsyncModule(id: string): null | Thenable { + // We've already loaded all the chunks. We can require the module. + const promise = parcelRequire(id); + if (typeof promise.then !== 'function') { + // This wasn't a promise after all. + return null; + } else if (promise.status === 'fulfilled') { + // This module was already resolved earlier. + return null; + } else { + // Instrument the Promise to stash the result. + promise.then( + value => { + const fulfilledThenable: FulfilledThenable = (promise: any); + fulfilledThenable.status = 'fulfilled'; + fulfilledThenable.value = value; + }, + reason => { + const rejectedThenable: RejectedThenable = (promise: any); + rejectedThenable.status = 'rejected'; + rejectedThenable.reason = reason; + }, + ); + return promise; + } +} + +function ignoreReject() { + // We rely on rejected promises to be handled by another listener. +} +// Start preloading the modules since we might need them soon. +// This function doesn't suspend. +export function preloadModule( + metadata: ClientReference, +): null | Thenable { + const chunks = metadata[CHUNKS]; + const promises = []; + let i = 0; + while (i < chunks.length) { + const chunkUrl = chunks[i++]; + const entry = chunkCache.get(chunkUrl); + if (entry === undefined) { + const thenable = loadChunk(chunkUrl); + promises.push(thenable); + // $FlowFixMe[method-unbinding] + const resolve = chunkCache.set.bind(chunkCache, chunkUrl, null); + thenable.then(resolve, ignoreReject); + chunkCache.set(chunkUrl, thenable); + } else if (entry !== null) { + promises.push(entry); + } + } + if (isAsyncImport(metadata)) { + if (promises.length === 0) { + return requireAsyncModule(metadata[ID]); + } else { + return Promise.all(promises).then(() => { + return requireAsyncModule(metadata[ID]); + }); + } + } else if (promises.length > 0) { + return Promise.all(promises); + } else { + return null; + } +} + +// Actually require the module or suspend if it's not yet ready. +// Increase priority if necessary. +export function requireModule(metadata: ClientReference): T { + let moduleExports = parcelRequire(metadata[ID]); + if (isAsyncImport(metadata)) { + if (typeof moduleExports.then !== 'function') { + // This wasn't a promise after all. + } else if (moduleExports.status === 'fulfilled') { + // This Promise should've been instrumented by preloadModule. + moduleExports = moduleExports.value; + } else { + throw moduleExports.reason; + } + } + if (metadata[NAME] === '*') { + // This is a placeholder value that represents that the caller imported this + // as a CommonJS module as is. + return moduleExports; + } + if (metadata[NAME] === '') { + // This is a placeholder value that represents that the caller accessed the + // default property of this if it was an ESM interop module. + return moduleExports.__esModule ? moduleExports.default : moduleExports; + } + return moduleExports[metadata[NAME]]; +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js new file mode 100644 index 0000000000000..6eff375cf8de0 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js @@ -0,0 +1,18 @@ +/** + * 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 function loadChunk(url: string): Promise { + if (url.endsWith('.css')) { + // TODO: move this to a separate package + const cssLoader = require('@parcel/runtime-js/src/helpers/browser/css-loader'); + return cssLoader(url); + } else { + return __parcel__import__('.' + url); + } +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js new file mode 100644 index 0000000000000..dc5fdcb7a3d63 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js @@ -0,0 +1,16 @@ +/** + * 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 function loadChunk(url: string): Promise { + if (url.endsWith('.css')) { + return Promise.resolve(); + } else { + return __parcel__import__('.' + url); + } +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser.js new file mode 100644 index 0000000000000..60b9e87dbea3e --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser.js @@ -0,0 +1,18 @@ +/** + * 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 type ModuleLoading = null; + +export function prepareDestinationWithChunks( + moduleLoading: ModuleLoading, + chunks: mixed, + nonce: ?string, +) { + // In the browser we don't need to prepare our destination since the browser is the Destination +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js new file mode 100644 index 0000000000000..7b0c13f947eff --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js @@ -0,0 +1,44 @@ +/** + * 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 + */ + +import {preloadModuleForSSR, preinitStyleForSSR} from 'react-client/src/ReactFlightClientConfig'; + +export type ModuleLoading = null | { + prefix: string, + crossOrigin?: 'use-credentials' | '', +}; + +export function prepareDestinationWithChunks( + moduleLoading: ModuleLoading, + chunks: Array, + nonce: ?string, +) { + if (moduleLoading !== null) { + for (let i = 0; i < chunks.length; i++) { + if (chunks[i].endsWith('.css')) { + preinitStyleForSSR( + moduleLoading.prefix + chunks[i], + null, // precedence?? + {crossOrigin: moduleLoading.crossOrigin} + ); + } else { + // Use preload rather than preinit so the script is not executed until its dependencies + // are ready. This happens once the parcelRequire call to execute the entry module occurs + // during bootstrapping. + preloadModuleForSSR( + moduleLoading.prefix + chunks[i], + { + nonce, + crossOrigin: moduleLoading.crossOrigin + }, + ); + } + } + } +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-parcel/src/ReactFlightDOMClientBrowser.js new file mode 100644 index 0000000000000..64e6b3886adf7 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightDOMClientBrowser.js @@ -0,0 +1,111 @@ +/** + * 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 + */ + +import type {Thenable} from 'shared/ReactTypes.js'; + +import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient'; + +import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient'; + +import { + createResponse, + getRoot, + reportGlobalError, + processBinaryChunk, + close, +} from 'react-client/src/ReactFlightClient'; + +import { + processReply, + createServerReference, +} from 'react-client/src/ReactFlightReplyClient'; + +type CallServerCallback = (string, args: A) => Promise; + +export type Options = { + callServer?: CallServerCallback, +}; + +function createResponseFromOptions(options: void | Options) { + return createResponse( + null, + null, + options && options.callServer ? options.callServer : undefined, + undefined, // nonce + ); +} + +function startReadingFromStream( + response: FlightResponse, + stream: ReadableStream, +): void { + const reader = stream.getReader(); + function progress({ + done, + value, + }: { + done: boolean, + value: ?any, + ... + }): void | Promise { + if (done) { + close(response); + return; + } + const buffer: Uint8Array = (value: any); + processBinaryChunk(response, buffer); + return reader.read().then(progress).catch(error); + } + function error(e: any) { + reportGlobalError(response, e); + } + reader.read().then(progress).catch(error); +} + +function createFromReadableStream( + stream: ReadableStream, + options?: Options, +): Thenable { + const response: FlightResponse = createResponseFromOptions(options); + startReadingFromStream(response, stream); + return getRoot(response); +} + +function createFromFetch( + promiseForResponse: Promise, + options?: Options, +): Thenable { + const response: FlightResponse = createResponseFromOptions(options); + promiseForResponse.then( + function (r) { + startReadingFromStream(response, (r.body: any)); + }, + function (e) { + reportGlobalError(response, e); + }, + ); + return getRoot(response); +} + +function encodeReply( + value: ReactServerValue, +): Promise< + string | URLSearchParams | FormData, +> /* We don't use URLSearchParams yet but maybe */ { + return new Promise((resolve, reject) => { + processReply(value, '', resolve, reject); + }); +} + +export { + createFromFetch, + createFromReadableStream, + encodeReply, + createServerReference, +}; diff --git a/packages/react-server-dom-parcel/src/ReactFlightDOMClientEdge.js b/packages/react-server-dom-parcel/src/ReactFlightDOMClientEdge.js new file mode 100644 index 0000000000000..d1aea295de51d --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightDOMClientEdge.js @@ -0,0 +1,130 @@ +/** + * 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 + */ + +import type {Thenable} from 'shared/ReactTypes.js'; + +import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient'; + +import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient'; + +import type { + SSRModuleMap, + ModuleLoading, +} from 'react-client/src/ReactFlightClientConfig'; + +type SSRManifest = { + moduleMap: SSRModuleMap, + moduleLoading: ModuleLoading, +}; + +import { + createResponse, + getRoot, + reportGlobalError, + processBinaryChunk, + close, +} from 'react-client/src/ReactFlightClient'; + +import { + processReply, + createServerReference as createServerReferenceImpl, +} from 'react-client/src/ReactFlightReplyClient'; + +function noServerCall() { + throw new Error( + 'Server Functions cannot be called during initial render. ' + + 'This would create a fetch waterfall. Try to use a Server Component ' + + 'to pass data to Client Components instead.', + ); +} + +export function createServerReference, T>( + id: any, + callServer: any, +): (...A) => Promise { + return createServerReferenceImpl(id, noServerCall); +} + +export type Options = { + ssrManifest: SSRManifest, + nonce?: string, +}; + +function createResponseFromOptions(options: Options) { + return createResponse( + options.ssrManifest.moduleMap, + options.ssrManifest.moduleLoading, + noServerCall, + typeof options.nonce === 'string' ? options.nonce : undefined, + ); +} + +function startReadingFromStream( + response: FlightResponse, + stream: ReadableStream, +): void { + const reader = stream.getReader(); + function progress({ + done, + value, + }: { + done: boolean, + value: ?any, + ... + }): void | Promise { + if (done) { + close(response); + return; + } + const buffer: Uint8Array = (value: any); + processBinaryChunk(response, buffer); + return reader.read().then(progress).catch(error); + } + function error(e: any) { + reportGlobalError(response, e); + } + reader.read().then(progress).catch(error); +} + +function createFromReadableStream( + stream: ReadableStream, + options: Options, +): Thenable { + const response: FlightResponse = createResponseFromOptions(options); + startReadingFromStream(response, stream); + return getRoot(response); +} + +function createFromFetch( + promiseForResponse: Promise, + options: Options, +): Thenable { + const response: FlightResponse = createResponseFromOptions(options); + promiseForResponse.then( + function (r) { + startReadingFromStream(response, (r.body: any)); + }, + function (e) { + reportGlobalError(response, e); + }, + ); + return getRoot(response); +} + +function encodeReply( + value: ReactServerValue, +): Promise< + string | URLSearchParams | FormData, +> /* We don't use URLSearchParams yet but maybe */ { + return new Promise((resolve, reject) => { + processReply(value, '', resolve, reject); + }); +} + +export {createFromFetch, createFromReadableStream, encodeReply}; diff --git a/packages/react-server-dom-parcel/src/ReactFlightDOMClientNode.js b/packages/react-server-dom-parcel/src/ReactFlightDOMClientNode.js new file mode 100644 index 0000000000000..db6f233d80dc4 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightDOMClientNode.js @@ -0,0 +1,76 @@ +/** + * 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 + */ + +import type {Thenable} from 'shared/ReactTypes.js'; + +import type {Response} from 'react-client/src/ReactFlightClient'; + +import type { + SSRModuleMap, + ModuleLoading, +} from 'react-client/src/ReactFlightClientConfig'; + +type SSRManifest = { + moduleMap: SSRModuleMap, + moduleLoading: ModuleLoading, +}; + +import type {Readable} from 'stream'; + +import { + createResponse, + getRoot, + reportGlobalError, + processBinaryChunk, + close, +} from 'react-client/src/ReactFlightClient'; + +import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient'; + +function noServerCall() { + throw new Error( + 'Server Functions cannot be called during initial render. ' + + 'This would create a fetch waterfall. Try to use a Server Component ' + + 'to pass data to Client Components instead.', + ); +} + +export function createServerReference, T>( + id: any, + callServer: any, +): (...A) => Promise { + return createServerReferenceImpl(id, noServerCall); +} + +export type Options = { + nonce?: string, +}; + +function createFromNodeStream( + stream: Readable, + ssrManifest: SSRManifest, + options?: Options, +): Thenable { + const response: Response = createResponse( + ssrManifest.moduleMap, + ssrManifest.moduleLoading, + noServerCall, + options && typeof options.nonce === 'string' ? options.nonce : undefined, + ); + stream.on('data', chunk => { + processBinaryChunk(response, chunk); + }); + stream.on('error', error => { + reportGlobalError(response, error); + }); + stream.on('end', () => close(response)); + return getRoot(response); +} + +export {createFromNodeStream}; diff --git a/packages/react-server-dom-parcel/src/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-parcel/src/ReactFlightDOMServerBrowser.js new file mode 100644 index 0000000000000..1136ccb360b64 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightDOMServerBrowser.js @@ -0,0 +1,102 @@ +/** + * 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 + */ + +import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; +import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes'; +import type {ClientManifest} from './ReactFlightServerConfigParcelBundler'; +import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig'; + +import { + createRequest, + startWork, + startFlowing, + stopFlowing, + abort, +} from 'react-server/src/ReactFlightServer'; + +import { + createResponse, + close, + getRoot, +} from 'react-server/src/ReactFlightReplyServer'; + +import { + decodeAction, + decodeFormState, +} from 'react-server/src/ReactFlightActionServer'; + +type Options = { + identifierPrefix?: string, + signal?: AbortSignal, + context?: Array<[string, ServerContextJSONValue]>, + onError?: (error: mixed) => void, + onPostpone?: (reason: string) => void, +}; + +function renderToReadableStream( + model: ReactClientValue, + manifest: ClientManifest, + options?: Options, +): ReadableStream { + const request = createRequest( + model, + manifest, + options ? options.onError : undefined, + options ? options.context : undefined, + options ? options.identifierPrefix : undefined, + options ? options.onPostpone : undefined, + ); + if (options && options.signal) { + const signal = options.signal; + if (signal.aborted) { + abort(request, (signal: any).reason); + } else { + const listener = () => { + abort(request, (signal: any).reason); + signal.removeEventListener('abort', listener); + }; + signal.addEventListener('abort', listener); + } + } + const stream = new ReadableStream( + { + type: 'bytes', + start: (controller): ?Promise => { + startWork(request); + }, + pull: (controller): ?Promise => { + startFlowing(request, controller); + }, + cancel: (reason): ?Promise => { + stopFlowing(request); + abort(request, reason); + }, + }, + // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. + {highWaterMark: 0}, + ); + return stream; +} + +function decodeReply( + body: string | FormData, + manifest: ServerManifest, +): Thenable { + if (typeof body === 'string') { + const form = new FormData(); + form.append('0', body); + body = form; + } + const response = createResponse(manifest, '', body); + const root = getRoot(response); + close(response); + return root; +} + +export {renderToReadableStream, decodeReply, decodeAction, decodeFormState}; diff --git a/packages/react-server-dom-parcel/src/ReactFlightDOMServerEdge.js b/packages/react-server-dom-parcel/src/ReactFlightDOMServerEdge.js new file mode 100644 index 0000000000000..1136ccb360b64 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightDOMServerEdge.js @@ -0,0 +1,102 @@ +/** + * 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 + */ + +import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; +import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes'; +import type {ClientManifest} from './ReactFlightServerConfigParcelBundler'; +import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig'; + +import { + createRequest, + startWork, + startFlowing, + stopFlowing, + abort, +} from 'react-server/src/ReactFlightServer'; + +import { + createResponse, + close, + getRoot, +} from 'react-server/src/ReactFlightReplyServer'; + +import { + decodeAction, + decodeFormState, +} from 'react-server/src/ReactFlightActionServer'; + +type Options = { + identifierPrefix?: string, + signal?: AbortSignal, + context?: Array<[string, ServerContextJSONValue]>, + onError?: (error: mixed) => void, + onPostpone?: (reason: string) => void, +}; + +function renderToReadableStream( + model: ReactClientValue, + manifest: ClientManifest, + options?: Options, +): ReadableStream { + const request = createRequest( + model, + manifest, + options ? options.onError : undefined, + options ? options.context : undefined, + options ? options.identifierPrefix : undefined, + options ? options.onPostpone : undefined, + ); + if (options && options.signal) { + const signal = options.signal; + if (signal.aborted) { + abort(request, (signal: any).reason); + } else { + const listener = () => { + abort(request, (signal: any).reason); + signal.removeEventListener('abort', listener); + }; + signal.addEventListener('abort', listener); + } + } + const stream = new ReadableStream( + { + type: 'bytes', + start: (controller): ?Promise => { + startWork(request); + }, + pull: (controller): ?Promise => { + startFlowing(request, controller); + }, + cancel: (reason): ?Promise => { + stopFlowing(request); + abort(request, reason); + }, + }, + // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. + {highWaterMark: 0}, + ); + return stream; +} + +function decodeReply( + body: string | FormData, + manifest: ServerManifest, +): Thenable { + if (typeof body === 'string') { + const form = new FormData(); + form.append('0', body); + body = form; + } + const response = createResponse(manifest, '', body); + const root = getRoot(response); + close(response); + return root; +} + +export {renderToReadableStream, decodeReply, decodeAction, decodeFormState}; diff --git a/packages/react-server-dom-parcel/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-parcel/src/ReactFlightDOMServerNode.js new file mode 100644 index 0000000000000..abb47d30dcf34 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightDOMServerNode.js @@ -0,0 +1,189 @@ +/** + * 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 + */ + +import type { + Request, + ReactClientValue, +} from 'react-server/src/ReactFlightServer'; +import type {Destination} from 'react-server/src/ReactServerStreamConfigNode'; +import type {ClientManifest} from './ReactFlightServerConfigParcelBundler'; +import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig'; +import type {Busboy} from 'busboy'; +import type {Writable} from 'stream'; +import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes'; + +import { + createRequest, + startWork, + startFlowing, + stopFlowing, + abort, +} from 'react-server/src/ReactFlightServer'; + +import { + createResponse, + reportGlobalError, + close, + resolveField, + resolveFileInfo, + resolveFileChunk, + resolveFileComplete, + getRoot, +} from 'react-server/src/ReactFlightReplyServer'; + +import { + decodeAction, + decodeFormState, +} from 'react-server/src/ReactFlightActionServer'; + +function createDrainHandler(destination: Destination, request: Request) { + return () => startFlowing(request, destination); +} + +function createCancelHandler(request: Request, reason: string) { + return () => { + stopFlowing(request); + // eslint-disable-next-line react-internal/prod-error-codes + abort(request, new Error(reason)); + }; +} + +type Options = { + onError?: (error: mixed) => void, + onPostpone?: (reason: string) => void, + context?: Array<[string, ServerContextJSONValue]>, + identifierPrefix?: string, +}; + +type PipeableStream = { + abort(reason: mixed): void, + pipe(destination: T): T, +}; + +function renderToPipeableStream( + model: ReactClientValue, + manifest: ClientManifest, + options?: Options, +): PipeableStream { + const request = createRequest( + model, + manifest, + options ? options.onError : undefined, + options ? options.context : undefined, + options ? options.identifierPrefix : undefined, + options ? options.onPostpone : undefined, + ); + let hasStartedFlowing = false; + startWork(request); + return { + pipe(destination: T): T { + if (hasStartedFlowing) { + throw new Error( + 'React currently only supports piping to one writable stream.', + ); + } + hasStartedFlowing = true; + startFlowing(request, destination); + destination.on('drain', createDrainHandler(destination, request)); + destination.on( + 'error', + createCancelHandler( + request, + 'The destination stream errored while writing data.', + ), + ); + destination.on( + 'close', + createCancelHandler(request, 'The destination stream closed early.'), + ); + return destination; + }, + abort(reason: mixed) { + abort(request, reason); + }, + }; +} + +function decodeReplyFromBusboy( + busboyStream: Busboy, + manifest: ServerManifest, +): Thenable { + const response = createResponse(manifest, ''); + let pendingFiles = 0; + const queuedFields: Array = []; + busboyStream.on('field', (name, value) => { + if (pendingFiles > 0) { + // Because the 'end' event fires two microtasks after the next 'field' + // we would resolve files and fields out of order. To handle this properly + // we queue any fields we receive until the previous file is done. + queuedFields.push(name, value); + } else { + resolveField(response, name, value); + } + }); + busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => { + if (encoding.toLowerCase() === 'base64') { + throw new Error( + "React doesn't accept base64 encoded file uploads because we don't expect " + + "form data passed from a browser to ever encode data that way. If that's " + + 'the wrong assumption, we can easily fix it.', + ); + } + pendingFiles++; + const file = resolveFileInfo(response, name, filename, mimeType); + value.on('data', chunk => { + resolveFileChunk(response, file, chunk); + }); + value.on('end', () => { + resolveFileComplete(response, name, file); + pendingFiles--; + if (pendingFiles === 0) { + // Release any queued fields + for (let i = 0; i < queuedFields.length; i += 2) { + resolveField(response, queuedFields[i], queuedFields[i + 1]); + } + queuedFields.length = 0; + } + }); + }); + busboyStream.on('finish', () => { + close(response); + }); + busboyStream.on('error', err => { + reportGlobalError( + response, + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible + err, + ); + }); + return getRoot(response); +} + +function decodeReply( + body: string | FormData, + manifest: ServerManifest, +): Thenable { + if (typeof body === 'string') { + const form = new FormData(); + form.append('0', body); + body = form; + } + const response = createResponse(manifest, '', body); + const root = getRoot(response); + close(response); + return root; +} + +export { + renderToPipeableStream, + decodeReplyFromBusboy, + decodeReply, + decodeAction, + decodeFormState, +}; diff --git a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js new file mode 100644 index 0000000000000..d5309d8bcd76a --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js @@ -0,0 +1,34 @@ +/** + * 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 + */ + +import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; + +export type ServerReference = T & { + $$typeof: symbol, + $$id: string, + $$bound: null | Array, +}; + +// eslint-disable-next-line no-unused-vars +export type ClientReference = { + $$typeof: symbol, + $$id: string, + $$async: boolean, +}; + +const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference'); +const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference'); + +export function isClientReference(reference: Object): boolean { + return reference.$$typeof === CLIENT_REFERENCE_TAG; +} + +export function isServerReference(reference: Object): boolean { + return reference.$$typeof === SERVER_REFERENCE_TAG; +} diff --git a/packages/react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler.js b/packages/react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler.js new file mode 100644 index 0000000000000..af53131ddeb47 --- /dev/null +++ b/packages/react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler.js @@ -0,0 +1,93 @@ +/** + * 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 + */ + +import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; +import type { + ImportMetadata, + ImportManifestEntry, +} from './shared/ReactFlightImportMetadata'; + +import type { + ClientReference, + ServerReference, +} from './ReactFlightParcelReferences'; + +export type {ClientReference, ServerReference}; + +export type ClientManifest = { + [id: string]: ClientReferenceManifestEntry, +}; + +export type ServerReferenceId = string; + +export type ClientReferenceMetadata = ImportMetadata; +export opaque type ClientReferenceManifestEntry = ImportManifestEntry; + +export type ClientReferenceKey = string; + +export { + isClientReference, + isServerReference, +} from './ReactFlightParcelReferences'; + +export function getClientReferenceKey( + reference: ClientReference, +): ClientReferenceKey { + return reference.$$async ? reference.$$id + '#async' : reference.$$id; +} + +export function resolveClientReferenceMetadata( + config: ClientManifest, + clientReference: ClientReference, +): ClientReferenceMetadata { + const modulePath = clientReference.$$id; + let name = ''; + let resolvedModuleData = config[modulePath]; + if (resolvedModuleData) { + // The potentially aliased name. + name = resolvedModuleData.name; + } else { + // We didn't find this specific export name but we might have the * export + // which contains this name as well. + // TODO: It's unfortunate that we now have to parse this string. We should + // probably go back to encoding path and name separately on the client reference. + const idx = modulePath.lastIndexOf('#'); + if (idx !== -1) { + name = modulePath.slice(idx + 1); + resolvedModuleData = config[modulePath.slice(0, idx)]; + } + if (!resolvedModuleData) { + throw new Error( + 'Could not find the module "' + + modulePath + + '" in the React Client Manifest. ' + + 'This is probably a bug in the React Server Components bundler.', + ); + } + } + if (clientReference.$$async === true) { + return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1]; + } else { + return [resolvedModuleData.id, resolvedModuleData.chunks, name]; + } +} + +export function getServerReferenceId( + config: ClientManifest, + serverReference: ServerReference, +): ServerReferenceId { + return serverReference.$$id; +} + +export function getServerReferenceBoundArguments( + config: ClientManifest, + serverReference: ServerReference, +): null | Array { + return serverReference.$$bound; +} diff --git a/packages/react-server-dom-parcel/src/shared/ReactFlightImportMetadata.js b/packages/react-server-dom-parcel/src/shared/ReactFlightImportMetadata.js new file mode 100644 index 0000000000000..08aafaf00c605 --- /dev/null +++ b/packages/react-server-dom-parcel/src/shared/ReactFlightImportMetadata.js @@ -0,0 +1,43 @@ +/** + * 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 type ImportManifestEntry = { + id: string, + // chunks is a double indexed array of chunkId / chunkFilename pairs + chunks: Array, + name: string, +}; + +// This is the parsed shape of the wire format which is why it is +// condensed to only the essentialy information +export type ImportMetadata = + | [ + /* id */ string, + /* chunks id/filename pairs, double indexed */ Array, + /* name */ string, + /* async */ 1, + ] + | [ + /* id */ string, + /* chunks id/filename pairs, double indexed */ Array, + /* name */ string, + ]; + +export const ID = 0; +export const CHUNKS = 1; +export const NAME = 2; +// export const ASYNC = 3; + +// This logic is correct because currently only include the 4th tuple member +// when the module is async. If that changes we will need to actually assert +// the value is true. We don't index into the 4th slot because flow does not +// like the potential out of bounds access +export function isAsyncImport(metadata: ImportMetadata): boolean { + return metadata.length === 4; +} diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js new file mode 100644 index 0000000000000..b7930fbef67fe --- /dev/null +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-parcel.js @@ -0,0 +1,18 @@ +/** + * 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 + */ + +import type {Request} from 'react-server/src/ReactFlightServer'; + +export * from 'react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler'; +export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; + +export const supportsRequestStorage = false; +export const requestStorage: AsyncLocalStorage = (null: any); + +export * from '../ReactFlightServerConfigDebugNoop'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js new file mode 100644 index 0000000000000..4899fd97415b8 --- /dev/null +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-edge-parcel.js @@ -0,0 +1,33 @@ +/** + * 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 + */ +import type {Request} from 'react-server/src/ReactFlightServer'; + +export * from 'react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler'; +export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; + +// For now, we get this from the global scope, but this will likely move to a module. +export const supportsRequestStorage = typeof AsyncLocalStorage === 'function'; +export const requestStorage: AsyncLocalStorage = supportsRequestStorage + ? new AsyncLocalStorage() + : (null: any); + +// We use the Node version but get access to async_hooks from a global. +import type {HookCallbacks, AsyncHook} from 'async_hooks'; +export const createAsyncHook: HookCallbacks => AsyncHook = + typeof async_hooks === 'object' + ? async_hooks.createHook + : function () { + return ({ + enable() {}, + disable() {}, + }: any); + }; +export const executionAsyncId: () => number = + typeof async_hooks === 'object' ? async_hooks.executionAsyncId : (null: any); +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js new file mode 100644 index 0000000000000..317716cb4c01f --- /dev/null +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-parcel.js @@ -0,0 +1,22 @@ +/** + * 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 + */ + +import {AsyncLocalStorage} from 'async_hooks'; + +import type {Request} from 'react-server/src/ReactFlightServer'; + +export * from 'react-server-dom-parcel/src/ReactFlightServerConfigParcelBundler'; +export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; + +export const supportsRequestStorage = true; +export const requestStorage: AsyncLocalStorage = + new AsyncLocalStorage(); + +export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks'; +export * from '../ReactFlightServerConfigDebugNode'; diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 19adb73dd7334..a35b117fe0e17 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -98,6 +98,9 @@ declare var __turbopack_require__: ((id: string) => any) & { u: string => string, }; +declare function __parcel__import__(id: string): Promise; +declare function parcelRequire(id: string): any; + declare module 'fs/promises' { declare var access: (path: string, mode?: number) => Promise; declare var lstat: ( diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index a4f3f08bbd522..f00f44ae6c451 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -546,6 +546,64 @@ const bundles = [ externals: ['url', 'module', 'react-server-dom-turbopack/server'], }, + /******* React Server DOM Parcel Server *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-parcel/server.browser', + global: 'ReactServerDOMServer', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'react-dom'], + }, + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-parcel/server.node', + global: 'ReactServerDOMServer', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'util', 'async_hooks', 'react-dom'], + }, + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-parcel/server.edge', + global: 'ReactServerDOMServer', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'util', 'async_hooks', 'react-dom'], + }, + + /******* React Server DOM Parcel Client *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-parcel/client.browser', + global: 'ReactServerDOMClient', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'react-dom'], + }, + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-parcel/client.node', + global: 'ReactServerDOMClient', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'react-dom', 'util'], + }, + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-parcel/client.edge', + global: 'ReactServerDOMClient', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'react-dom'], + }, + /******* React Server DOM ESM Server *******/ { bundleTypes: [NODE_DEV, NODE_PROD], diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index fa4b9cb979cf5..28cdf156f33d8 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -64,6 +64,10 @@ module.exports = { __turbopack_load__: 'readonly', __turbopack_require__: 'readonly', + // Flight Parcel + parcelRequire: 'readonly', + __parcel__import__: 'readonly', + // jest expect: 'readonly', jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 64d83ff90c241..ed25d56e2dad7 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -62,6 +62,10 @@ module.exports = { __turbopack_load__: 'readonly', __turbopack_require__: 'readonly', + // Flight Parcel + parcelRequire: 'readonly', + __parcel__import__: 'readonly', + // jest expect: 'readonly', jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index 80572e85cd8c3..c31ecad5b5ded 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -64,6 +64,10 @@ module.exports = { __turbopack_load__: 'readonly', __turbopack_require__: 'readonly', + // Flight Parcel + parcelRequire: 'readonly', + __parcel__import__: 'readonly', + // jest expect: 'readonly', jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index 800fd645ae614..3bc9ffd6480ce 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -69,6 +69,10 @@ module.exports = { __turbopack_load__: 'readonly', __turbopack_require__: 'readonly', + // Flight Parcel + parcelRequire: 'readonly', + __parcel__import__: 'readonly', + // jest jest: 'readonly', diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index fa6916cee621b..33ecc620bdd04 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -162,6 +162,41 @@ module.exports = [ isFlowTyped: true, isServerSupported: true, }, + { + shortName: 'dom-node-parcel', + entryPoints: [ + 'react-server-dom-parcel/server.node', + 'react-server-dom-parcel/client.node', + ], + paths: [ + 'react-dom', + 'react-dom-bindings', + 'react-dom/client', + 'react-dom/server', + 'react-dom/server.node', + 'react-dom/static', + 'react-dom/static.node', + 'react-dom/src/server/react-dom-server.node', + 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node + 'react-dom/src/server/ReactDOMFizzStaticNode.js', + 'react-server-dom-parcel', + 'react-server-dom-parcel/client.node', + 'react-server-dom-parcel/server', + 'react-server-dom-parcel/server.node', + 'react-server-dom-parcel/src/ReactFlightDOMServerNode.js', // react-server-dom-parcel/server.node + 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js', + 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js', + 'react-devtools', + 'react-devtools-core', + 'react-devtools-shell', + 'react-devtools-shared', + 'react-interactions', + 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', + ], + isFlowTyped: true, + isServerSupported: true, + }, { shortName: 'dom-bun', entryPoints: ['react-dom', 'react-dom/src/server/react-dom-server.bun.js'], @@ -270,6 +305,36 @@ module.exports = [ isFlowTyped: true, isServerSupported: true, }, + { + shortName: 'dom-browser-parcel', + entryPoints: [ + 'react-server-dom-parcel/client.browser', + 'react-server-dom-parcel/server.browser', + ], + paths: [ + 'react-dom', + 'react-dom/client', + 'react-dom/server', + 'react-dom/server.node', + 'react-dom-bindings', + 'react-server-dom-parcel', + 'react-server-dom-parcel/client', + 'react-server-dom-parcel/client.browser', + 'react-server-dom-parcel/server.browser', + 'react-server-dom-parcel/src/ReactFlightDOMClientBrowser.js', // react-server-dom-parcel/client.browser + 'react-server-dom-parcel/src/ReactFlightDOMServerBrowser.js', // react-server-dom-parcel/server.browser + 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js', + 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js', + 'react-devtools', + 'react-devtools-core', + 'react-devtools-shell', + 'react-devtools-shared', + 'react-interactions', + 'shared/ReactDOMSharedInternals', + ], + isFlowTyped: true, + isServerSupported: true, + }, { shortName: 'dom-edge-webpack', entryPoints: [ @@ -340,6 +405,40 @@ module.exports = [ isFlowTyped: true, isServerSupported: true, }, + { + shortName: 'dom-edge-parcel', + entryPoints: [ + 'react-server-dom-parcel/server.edge', + 'react-server-dom-parcel/client.edge', + ], + paths: [ + 'react-dom', + 'react-dom/src/ReactDOMServer.js', + 'react-dom-bindings', + 'react-dom/client', + 'react-dom/server.edge', + 'react-dom/static.edge', + 'react-dom/unstable_testing', + 'react-dom/src/server/react-dom-server.edge', + 'react-dom/src/server/ReactDOMFizzServerEdge.js', // react-dom/server.edge + 'react-dom/src/server/ReactDOMFizzStaticEdge.js', + 'react-server-dom-parcel', + 'react-server-dom-parcel/client.edge', + 'react-server-dom-parcel/server.edge', + 'react-server-dom-parcel/src/ReactFlightDOMClientEdge.js', // react-server-dom-parcel/client.edge + 'react-server-dom-parcel/src/ReactFlightDOMServerEdge.js', // react-server-dom-parcel/server.edge + 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js', + 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js', + 'react-devtools', + 'react-devtools-core', + 'react-devtools-shell', + 'react-devtools-shared', + 'shared/ReactDOMSharedInternals', + 'react-server/src/ReactFlightServerConfigDebugNode.js', + ], + isFlowTyped: true, + isServerSupported: true, + }, { shortName: 'dom-node-esm', entryPoints: [ From 469017618df21c3f9f0d3caba17135832dd47eb7 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 27 Jan 2024 22:07:22 -0500 Subject: [PATCH 02/22] lint --- .../src/shared/ReactFlightClientConfigDOM.js | 10 +++++++--- ...ctFlightClientConfigBundlerParcelBrowser.js | 1 + ...eactFlightClientConfigTargetParcelServer.js | 18 +++++++++--------- scripts/rollup/validate/eslintrc.cjs.js | 2 +- scripts/rollup/validate/eslintrc.cjs2015.js | 2 +- scripts/rollup/validate/eslintrc.umd.js | 2 +- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js index 9b0582cde8c3e..b2d83a38cbc97 100644 --- a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js +++ b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js @@ -11,7 +11,11 @@ // It is the configuration of the FlightClient behavior which can run in either environment. import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM'; -import type {PreinitStyleOptions, PreloadImplOptions, PreloadModuleImplOptions} from 'react-dom/src/shared/ReactDOMTypes'; +import type { + PreinitStyleOptions, + PreloadImplOptions, + PreloadModuleImplOptions, +} from 'react-dom/src/shared/ReactDOMTypes'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; @@ -145,7 +149,7 @@ export function preinitScriptForSSR( export function preloadForSSR( href: string, as: string, - options: ?PreloadImplOptions + options: ?PreloadImplOptions, ) { const dispatcher = ReactDOMCurrentDispatcher.current; if (dispatcher) { @@ -155,7 +159,7 @@ export function preloadForSSR( export function preloadModuleForSSR( href: string, - options: ?PreloadModuleImplOptions + options: ?PreloadModuleImplOptions, ) { const dispatcher = ReactDOMCurrentDispatcher.current; if (dispatcher) { diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js index 6eff375cf8de0..eeaff44b1dd79 100644 --- a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js @@ -10,6 +10,7 @@ export function loadChunk(url: string): Promise { if (url.endsWith('.css')) { // TODO: move this to a separate package + // $FlowFixMe const cssLoader = require('@parcel/runtime-js/src/helpers/browser/css-loader'); return cssLoader(url); } else { diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js index 7b0c13f947eff..d3e13a379132c 100644 --- a/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js @@ -7,7 +7,10 @@ * @flow */ -import {preloadModuleForSSR, preinitStyleForSSR} from 'react-client/src/ReactFlightClientConfig'; +import { + preloadModuleForSSR, + preinitStyleForSSR, +} from 'react-client/src/ReactFlightClientConfig'; export type ModuleLoading = null | { prefix: string, @@ -25,19 +28,16 @@ export function prepareDestinationWithChunks( preinitStyleForSSR( moduleLoading.prefix + chunks[i], null, // precedence?? - {crossOrigin: moduleLoading.crossOrigin} + {crossOrigin: moduleLoading.crossOrigin}, ); } else { // Use preload rather than preinit so the script is not executed until its dependencies // are ready. This happens once the parcelRequire call to execute the entry module occurs // during bootstrapping. - preloadModuleForSSR( - moduleLoading.prefix + chunks[i], - { - nonce, - crossOrigin: moduleLoading.crossOrigin - }, - ); + preloadModuleForSSR(moduleLoading.prefix + chunks[i], { + nonce, + crossOrigin: moduleLoading.crossOrigin, + }); } } } diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index 28cdf156f33d8..2bae7e136c4e3 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -67,7 +67,7 @@ module.exports = { // Flight Parcel parcelRequire: 'readonly', __parcel__import__: 'readonly', - + // jest expect: 'readonly', jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index ed25d56e2dad7..b001002a7b68a 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -65,7 +65,7 @@ module.exports = { // Flight Parcel parcelRequire: 'readonly', __parcel__import__: 'readonly', - + // jest expect: 'readonly', jest: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index 3bc9ffd6480ce..495ab6d266883 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -72,7 +72,7 @@ module.exports = { // Flight Parcel parcelRequire: 'readonly', __parcel__import__: 'readonly', - + // jest jest: 'readonly', From 409b66dce0426394392265400a1b73831d965a4c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 28 Jan 2024 17:42:14 -0500 Subject: [PATCH 03/22] remove unused file --- .../src/ReactFlightClientConfigBundlerNode.js | 156 ------------------ 1 file changed, 156 deletions(-) delete mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js deleted file mode 100644 index f1e5c5dd579bd..0000000000000 --- a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerNode.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * 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 - */ - -import type { - Thenable, - FulfilledThenable, - RejectedThenable, -} from 'shared/ReactTypes'; - -import type {ImportMetadata} from './shared/ReactFlightImportMetadata'; -import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig'; - -import { - ID, - CHUNKS, - NAME, - isAsyncImport, -} from './shared/ReactFlightImportMetadata'; -import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig'; - -export type SSRModuleMap = { - [clientId: string]: { - [clientExportName: string]: ClientReference, - }, -}; - -export type ServerManifest = void; - -export type ServerReferenceId = string; - -export opaque type ClientReferenceMetadata = ImportMetadata; - -// eslint-disable-next-line no-unused-vars -export opaque type ClientReference = { - specifier: string, - name: string, - async?: boolean, -}; - -export function prepareDestinationForModule( - moduleLoading: ModuleLoading, - nonce: ?string, - metadata: ClientReferenceMetadata, -) { - prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce); -} - -export function resolveClientReference( - bundlerConfig: SSRModuleMap, - metadata: ClientReferenceMetadata, -): ClientReference { - const moduleExports = bundlerConfig[metadata[ID]]; - let resolvedModuleData = moduleExports[metadata[NAME]]; - let name; - if (resolvedModuleData) { - // The potentially aliased name. - name = resolvedModuleData.name; - } else { - // If we don't have this specific name, we might have the full module. - resolvedModuleData = moduleExports['*']; - if (!resolvedModuleData) { - throw new Error( - 'Could not find the module "' + - metadata[ID] + - '" in the React SSR Manifest. ' + - 'This is probably a bug in the React Server Components bundler.', - ); - } - name = metadata[NAME]; - } - return { - specifier: resolvedModuleData.specifier, - name: name, - async: isAsyncImport(metadata), - }; -} - -export function resolveServerReference( - bundlerConfig: ServerManifest, - id: ServerReferenceId, -): ClientReference { - const idx = id.lastIndexOf('#'); - const specifier = id.slice(0, idx); - const name = id.slice(idx + 1); - return {specifier, name}; -} - -const asyncModuleCache: Map> = new Map(); - -export function preloadModule( - metadata: ClientReference, -): null | Thenable { - const existingPromise = asyncModuleCache.get(metadata.specifier); - if (existingPromise) { - if (existingPromise.status === 'fulfilled') { - return null; - } - return existingPromise; - } else { - // $FlowFixMe[unsupported-syntax] - let modulePromise: Promise = import(metadata.specifier); - if (metadata.async) { - // If the module is async, it must have been a CJS module. - // CJS modules are accessed through the default export in - // Node.js so we have to get the default export to get the - // full module exports. - modulePromise = modulePromise.then(function (value) { - return (value: any).default; - }); - } - modulePromise.then( - value => { - const fulfilledThenable: FulfilledThenable = - (modulePromise: any); - fulfilledThenable.status = 'fulfilled'; - fulfilledThenable.value = value; - }, - reason => { - const rejectedThenable: RejectedThenable = (modulePromise: any); - rejectedThenable.status = 'rejected'; - rejectedThenable.reason = reason; - }, - ); - asyncModuleCache.set(metadata.specifier, modulePromise); - return modulePromise; - } -} - -export function requireModule(metadata: ClientReference): T { - let moduleExports; - // We assume that preloadModule has been called before, which - // should have added something to the module cache. - const promise: any = asyncModuleCache.get(metadata.specifier); - if (promise.status === 'fulfilled') { - moduleExports = promise.value; - } else { - throw promise.reason; - } - if (metadata.name === '*') { - // This is a placeholder value that represents that the caller imported this - // as a CommonJS module as is. - return moduleExports; - } - if (metadata.name === '') { - // This is a placeholder value that represents that the caller accessed the - // default property of this if it was an ESM interop module. - return moduleExports.default; - } - return moduleExports[metadata.name]; -} From ba0ee14e89d717026f94c77aec80b9a6869aa2b3 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 17 Feb 2024 21:32:05 -0500 Subject: [PATCH 04/22] simplify --- .eslintrc.js | 1 - ...ctFlightClientConfig.dom-browser-parcel.js | 2 - ...ReactFlightClientConfig.dom-edge-parcel.js | 2 - ...ReactFlightClientConfig.dom-node-parcel.js | 2 - .../src/shared/ReactFlightClientConfigDOM.js | 37 ---- .../ReactFlightClientConfigBundlerParcel.js | 181 ++---------------- ...tFlightClientConfigBundlerParcelBrowser.js | 19 -- ...ctFlightClientConfigBundlerParcelServer.js | 16 -- ...ctFlightClientConfigTargetParcelBrowser.js | 18 -- ...actFlightClientConfigTargetParcelServer.js | 44 ----- .../src/ReactFlightDOMClientEdge.js | 24 +-- .../src/ReactFlightDOMClientNode.js | 17 +- .../src/ReactFlightParcelReferences.js | 4 +- .../ReactFlightServerConfigParcelBundler.js | 38 +--- .../src/shared/ReactFlightImportMetadata.js | 27 +-- scripts/flow/environment.js | 1 - scripts/rollup/validate/eslintrc.cjs.js | 1 - scripts/rollup/validate/eslintrc.cjs2015.js | 1 - scripts/rollup/validate/eslintrc.esm.js | 1 - scripts/rollup/validate/eslintrc.umd.js | 1 - scripts/shared/inlinedHostConfigs.js | 3 - 21 files changed, 35 insertions(+), 405 deletions(-) delete mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser.js delete mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer.js delete mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser.js delete mode 100644 packages/react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer.js diff --git a/.eslintrc.js b/.eslintrc.js index f63deeab0243b..0bfca3e502678 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -441,7 +441,6 @@ module.exports = { files: ['packages/react-server-dom-parcel/**/*.js'], globals: { parcelRequire: 'readonly', - __parcel__import__: 'readonly', }, }, { diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js index 0dc2dd30620b1..814b6639b8e22 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-parcel.js @@ -9,7 +9,5 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser'; export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel'; -export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelBrowser'; -export * from 'react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelBrowser'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; export const usedWithSSR = false; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js index ec09b570a841c..de508a0caf013 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-parcel.js @@ -9,7 +9,5 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser'; export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel'; -export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer'; -export * from 'react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; export const usedWithSSR = true; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js index 8bb2393b2c5e6..750e39d0e7db5 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-parcel.js @@ -9,7 +9,5 @@ export * from 'react-client/src/ReactFlightClientConfigNode'; export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel'; -export * from 'react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcelServer'; -export * from 'react-server-dom-parcel/src/ReactFlightClientConfigTargetParcelServer'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; export const usedWithSSR = true; diff --git a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js index b2d83a38cbc97..18ef25b06842e 100644 --- a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js +++ b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js @@ -11,11 +11,6 @@ // It is the configuration of the FlightClient behavior which can run in either environment. import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM'; -import type { - PreinitStyleOptions, - PreloadImplOptions, - PreloadModuleImplOptions, -} from 'react-dom/src/shared/ReactDOMTypes'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; @@ -145,35 +140,3 @@ export function preinitScriptForSSR( }); } } - -export function preloadForSSR( - href: string, - as: string, - options: ?PreloadImplOptions, -) { - const dispatcher = ReactDOMCurrentDispatcher.current; - if (dispatcher) { - dispatcher.preload(href, as, options); - } -} - -export function preloadModuleForSSR( - href: string, - options: ?PreloadModuleImplOptions, -) { - const dispatcher = ReactDOMCurrentDispatcher.current; - if (dispatcher) { - dispatcher.preloadModule(href, options); - } -} - -export function preinitStyleForSSR( - href: string, - precedence: ?string, - options?: ?PreinitStyleOptions, -) { - const dispatcher = ReactDOMCurrentDispatcher.current; - if (dispatcher) { - dispatcher.preinitStyle(href, precedence, options); - } -} diff --git a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js index 92a172b79b360..3d65f07288f97 100644 --- a/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js +++ b/packages/react-server-dom-parcel/src/ReactFlightClientConfigBundlerParcel.js @@ -7,40 +7,24 @@ * @flow */ -import type { - Thenable, - FulfilledThenable, - RejectedThenable, -} from 'shared/ReactTypes'; +import type {Thenable} from 'shared/ReactTypes'; import type { ImportMetadata, ImportManifestEntry, } from './shared/ReactFlightImportMetadata'; -import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig'; - -import { - ID, - CHUNKS, - NAME, - isAsyncImport, -} from './shared/ReactFlightImportMetadata'; -import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig'; - -import {loadChunk} from 'react-client/src/ReactFlightClientConfig'; - -export type SSRModuleMap = null | { - [clientId: string]: { - [clientExportName: string]: ClientReferenceManifestEntry, - }, -}; +import {ID, NAME} from './shared/ReactFlightImportMetadata'; export type ServerManifest = { - [id: string]: ImportManifestEntry, + [filePath: string]: { + [name: string]: ImportManifestEntry, + }, }; -export type ServerReferenceId = string; +export type SSRModuleMap = null; +export type ModuleLoading = null; +export type ServerReferenceId = ImportManifestEntry; export opaque type ClientReferenceManifestEntry = ImportManifestEntry; export opaque type ClientReferenceMetadata = ImportMetadata; @@ -49,170 +33,39 @@ export opaque type ClientReferenceMetadata = ImportMetadata; export opaque type ClientReference = ClientReferenceMetadata; export function prepareDestinationForModule( - moduleLoading: ModuleLoading, + moduleLoading: null, nonce: ?string, metadata: ClientReferenceMetadata, ) { - prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce); + return; } export function resolveClientReference( - bundlerConfig: SSRModuleMap, + bundlerConfig: null, metadata: ClientReferenceMetadata, ): ClientReference { - if (bundlerConfig) { - const moduleExports = bundlerConfig[metadata[ID]]; - let resolvedModuleData = moduleExports[metadata[NAME]]; - let name; - if (resolvedModuleData) { - // The potentially aliased name. - name = resolvedModuleData.name; - } else { - // If we don't have this specific name, we might have the full module. - resolvedModuleData = moduleExports['*']; - if (!resolvedModuleData) { - throw new Error( - 'Could not find the module "' + - metadata[ID] + - '" in the React SSR Manifest. ' + - 'This is probably a bug in the React Server Components bundler.', - ); - } - name = metadata[NAME]; - } - if (isAsyncImport(metadata)) { - return [ - resolvedModuleData.id, - resolvedModuleData.chunks, - name, - 1 /* async */, - ]; - } else { - return [resolvedModuleData.id, resolvedModuleData.chunks, name]; - } - } return metadata; } export function resolveServerReference( bundlerConfig: ServerManifest, - id: ServerReferenceId, + ref: ServerReferenceId, ): ClientReference { - let name = ''; - let resolvedModuleData = bundlerConfig[id]; - if (resolvedModuleData) { - // The potentially aliased name. - name = resolvedModuleData.name; - } else { - // We didn't find this specific export name but we might have the * export - // which contains this name as well. - // TODO: It's unfortunate that we now have to parse this string. We should - // probably go back to encoding path and name separately on the client reference. - const idx = id.lastIndexOf('#'); - if (idx !== -1) { - name = id.slice(idx + 1); - resolvedModuleData = bundlerConfig[id.slice(0, idx)]; - } - if (!resolvedModuleData) { - throw new Error( - 'Could not find the module "' + - id + - '" in the React Server Manifest. ' + - 'This is probably a bug in the React Server Components bundler.', - ); - } - } - // TODO: This needs to return async: true if it's an async module. - return [resolvedModuleData.id, resolvedModuleData.chunks, name]; -} - -// The chunk cache contains all the chunks we've preloaded so far. -// If they're still pending they're a thenable. This map also exists -// in Webpack but unfortunately it's not exposed so we have to -// replicate it in user space. null means that it has already loaded. -const chunkCache: Map> = new Map(); - -function requireAsyncModule(id: string): null | Thenable { - // We've already loaded all the chunks. We can require the module. - const promise = parcelRequire(id); - if (typeof promise.then !== 'function') { - // This wasn't a promise after all. - return null; - } else if (promise.status === 'fulfilled') { - // This module was already resolved earlier. - return null; - } else { - // Instrument the Promise to stash the result. - promise.then( - value => { - const fulfilledThenable: FulfilledThenable = (promise: any); - fulfilledThenable.status = 'fulfilled'; - fulfilledThenable.value = value; - }, - reason => { - const rejectedThenable: RejectedThenable = (promise: any); - rejectedThenable.status = 'rejected'; - rejectedThenable.reason = reason; - }, - ); - return promise; - } + const resolvedModuleData = bundlerConfig[ref.id][ref.name]; + return [resolvedModuleData.id, resolvedModuleData.name]; } -function ignoreReject() { - // We rely on rejected promises to be handled by another listener. -} -// Start preloading the modules since we might need them soon. -// This function doesn't suspend. export function preloadModule( metadata: ClientReference, ): null | Thenable { - const chunks = metadata[CHUNKS]; - const promises = []; - let i = 0; - while (i < chunks.length) { - const chunkUrl = chunks[i++]; - const entry = chunkCache.get(chunkUrl); - if (entry === undefined) { - const thenable = loadChunk(chunkUrl); - promises.push(thenable); - // $FlowFixMe[method-unbinding] - const resolve = chunkCache.set.bind(chunkCache, chunkUrl, null); - thenable.then(resolve, ignoreReject); - chunkCache.set(chunkUrl, thenable); - } else if (entry !== null) { - promises.push(entry); - } - } - if (isAsyncImport(metadata)) { - if (promises.length === 0) { - return requireAsyncModule(metadata[ID]); - } else { - return Promise.all(promises).then(() => { - return requireAsyncModule(metadata[ID]); - }); - } - } else if (promises.length > 0) { - return Promise.all(promises); - } else { - return null; - } + // Module should already be loaded due to