From 7464cb897956999e3940d1da592a3752c1b0c539 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 28 Jun 2023 21:49:10 -0400 Subject: [PATCH 1/5] Client and Server Reference Creation into Runtime We already did this for Server References on the Client so this brings us parity with that. This gives us some more flexibility with changing the runtime implementation without having to affect the loaders. We can also do more in the runtime such as adding .bind() support to Server References. --- .../src/ReactFlightDOMServerNode.js | 5 ++ .../src/ReactFlightESMNodeLoader.js | 31 ++++--- .../src/ReactFlightESMReferences.js | 74 ++++++++++++++++ .../src/ReactFlightServerConfigESMBundler.js | 30 ++----- .../src/ReactFlightDOMServerBrowser.js | 5 ++ .../src/ReactFlightDOMServerEdge.js | 5 ++ .../src/ReactFlightDOMServerNode.js | 5 ++ .../ReactFlightServerConfigWebpackBundler.js | 34 +++---- .../src/ReactFlightWebpackNodeLoader.js | 31 ++++--- .../src/ReactFlightWebpackNodeRegister.js | 88 ++++++------------- .../src/ReactFlightWebpackReferences.js | 74 ++++++++++++++++ scripts/rollup/bundles.js | 2 +- 12 files changed, 252 insertions(+), 132 deletions(-) create mode 100644 packages/react-server-dom-esm/src/ReactFlightESMReferences.js create mode 100644 packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js diff --git a/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js index 353bc515e4ee3..5b9c785a06894 100644 --- a/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js @@ -38,6 +38,11 @@ import { import {decodeAction} from 'react-server/src/ReactFlightActionServer'; +export { + registerServerReference, + registerClientReference, +} from './ReactFlightESMReferences'; + function createDrainHandler(destination: Destination, request: Request) { return () => startFlowing(request, destination); } diff --git a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js index 1e03a453474c1..ef53556690ae7 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js @@ -178,18 +178,20 @@ function transformServerModule( continue; } } - + if (localNames.size === 0) { + return source; + } let newSrc = source + '\n\n;'; + newSrc += + 'import {registerServerReference} from "react-server-dom-esm/server";\n'; localNames.forEach(function (exported, local) { if (localTypes.get(local) !== 'function') { // We first check if the export is a function and if so annotate it. newSrc += 'if (typeof ' + local + ' === "function") '; } - newSrc += 'Object.defineProperties(' + local + ',{'; - newSrc += '$$typeof: {value: Symbol.for("react.server.reference")},'; - newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + exported) + '},'; - newSrc += '$$bound: { value: null }'; - newSrc += '});\n'; + newSrc += 'registerServerReference(' + local + ','; + newSrc += JSON.stringify(url + '#' + exported); + newSrc += ');\n'; }); return newSrc; } @@ -313,13 +315,17 @@ async function transformClientModule( await parseExportNamesInto(body, names, url, loader); + if (names.length === 0) { + return ''; + } + let newSrc = - "const CLIENT_REFERENCE = Symbol.for('react.client.reference');\n"; + 'import {registerClientReference} from "react-server-dom-esm/server";\n'; for (let i = 0; i < names.length; i++) { const name = names[i]; if (name === 'default') { newSrc += 'export default '; - newSrc += 'Object.defineProperties(function() {'; + newSrc += 'registerClientReference(function() {'; newSrc += 'throw new Error(' + JSON.stringify( @@ -331,7 +337,7 @@ async function transformClientModule( ');'; } else { newSrc += 'export const ' + name + ' = '; - newSrc += 'Object.defineProperties(function() {'; + newSrc += 'registerClientReference(function() {'; newSrc += 'throw new Error(' + JSON.stringify( @@ -341,10 +347,9 @@ async function transformClientModule( ) + ');'; } - newSrc += '},{'; - newSrc += '$$typeof: {value: CLIENT_REFERENCE},'; - newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + name) + '}'; - newSrc += '});\n'; + newSrc += '},'; + newSrc += JSON.stringify(url + '#' + name); + newSrc += ');\n'; } return newSrc; } diff --git a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js new file mode 100644 index 0000000000000..bf7fbc2e134e5 --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js @@ -0,0 +1,74 @@ +/** + * 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, +}; + +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; +} + +export function registerClientReference( + proxyImplementation: any, + id: string, + async: boolean, +): ClientReference { + return Object.defineProperties(proxyImplementation, { + $$typeof: {value: CLIENT_REFERENCE_TAG}, + $$id: {value: id}, + $$async: {value: async}, + }); +} + +// $FlowFixMe[method-unbinding] +const FunctionBind = Function.prototype.bind; +// $FlowFixMe[method-unbinding] +const ArraySlice = Array.prototype.slice; +function bind(this: ServerReference) { + // $FlowFixMe[unsupported-syntax] + const newFn = FunctionBind.apply(this, arguments); + if (this.$$typeof === SERVER_REFERENCE_TAG) { + // $FlowFixMe[method-unbinding] + const args = ArraySlice.call(arguments, 1); + newFn.$$typeof = SERVER_REFERENCE_TAG; + newFn.$$id = this.$$id; + newFn.$$bound = this.$$bound ? this.$$bound.concat(args) : args; + } + return newFn; +} + +export function registerServerReference( + reference: ServerReference, + id: string, +): ServerReference { + return Object.defineProperties((reference: any), { + $$typeof: {value: SERVER_REFERENCE_TAG}, + $$id: {value: id}, + $$bound: {value: null}, + bind: {value: bind}, + }); +} diff --git a/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js b/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js index 3fc97067db0a8..97e32eeef76b3 100644 --- a/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js +++ b/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js @@ -9,21 +9,16 @@ import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; -export type ClientManifest = string; // base URL on the file system +import type { + ClientReference, + ServerReference, +} from './ReactFlightESMReferences'; -export type ServerReference = T & { - $$typeof: symbol, - $$id: string, - $$bound: null | Array, -}; +export type {ClientReference, ServerReference}; -export type ServerReferenceId = string; +export type ClientManifest = string; // base URL on the file system -// eslint-disable-next-line no-unused-vars -export type ClientReference = { - $$typeof: symbol, - $$id: string, -}; +export type ServerReferenceId = string; export type ClientReferenceMetadata = [ string, // module path @@ -32,8 +27,7 @@ export type ClientReferenceMetadata = [ export type ClientReferenceKey = string; -const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference'); -const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference'); +export {isClientReference, isServerReference} from './ReactFlightESMReferences'; export function getClientReferenceKey( reference: ClientReference, @@ -41,14 +35,6 @@ export function getClientReferenceKey( return reference.$$id; } -export function isClientReference(reference: Object): boolean { - return reference.$$typeof === CLIENT_REFERENCE_TAG; -} - -export function isServerReference(reference: Object): boolean { - return reference.$$typeof === SERVER_REFERENCE_TAG; -} - export function resolveClientReferenceMetadata( config: ClientManifest, clientReference: ClientReference, diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js index 24db03db06669..404253dfd162f 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js @@ -27,6 +27,11 @@ import { import {decodeAction} from 'react-server/src/ReactFlightActionServer'; +export { + registerServerReference, + registerClientReference, +} from './ReactFlightWebpackReferences'; + type Options = { identifierPrefix?: string, signal?: AbortSignal, diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js index 24db03db06669..404253dfd162f 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js @@ -27,6 +27,11 @@ import { import {decodeAction} from 'react-server/src/ReactFlightActionServer'; +export { + registerServerReference, + registerClientReference, +} from './ReactFlightWebpackReferences'; + type Options = { identifierPrefix?: string, signal?: AbortSignal, diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js index b4a9ecada1a2e..18d4b27dadb64 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js @@ -38,6 +38,11 @@ import { import {decodeAction} from 'react-server/src/ReactFlightActionServer'; +export { + registerServerReference, + registerClientReference, +} from './ReactFlightWebpackReferences'; + function createDrainHandler(destination: Destination, request: Request) { return () => startFlowing(request, destination); } diff --git a/packages/react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler.js b/packages/react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler.js index a24583c315e0a..b217ac1ef21fe 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler.js +++ b/packages/react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler.js @@ -9,25 +9,19 @@ import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; +import type { + ClientReference, + ServerReference, +} from './ReactFlightWebpackReferences'; + +export type {ClientReference, ServerReference}; + export type ClientManifest = { [id: string]: ClientReferenceMetadata, }; -export type ServerReference = T & { - $$typeof: symbol, - $$id: string, - $$bound: null | Array, -}; - export type ServerReferenceId = string; -// eslint-disable-next-line no-unused-vars -export type ClientReference = { - $$typeof: symbol, - $$id: string, - $$async: boolean, -}; - export type ClientReferenceMetadata = { id: string, chunks: Array, @@ -37,8 +31,10 @@ export type ClientReferenceMetadata = { export type ClientReferenceKey = string; -const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference'); -const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference'); +export { + isClientReference, + isServerReference, +} from './ReactFlightWebpackReferences'; export function getClientReferenceKey( reference: ClientReference, @@ -46,14 +42,6 @@ export function getClientReferenceKey( return reference.$$async ? reference.$$id + '#async' : reference.$$id; } -export function isClientReference(reference: Object): boolean { - return reference.$$typeof === CLIENT_REFERENCE_TAG; -} - -export function isServerReference(reference: Object): boolean { - return reference.$$typeof === SERVER_REFERENCE_TAG; -} - export function resolveClientReferenceMetadata( config: ClientManifest, clientReference: ClientReference, diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js index 1e03a453474c1..0406c1c96455e 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js @@ -178,18 +178,20 @@ function transformServerModule( continue; } } - + if (localNames.size === 0) { + return source; + } let newSrc = source + '\n\n;'; + newSrc += + 'import {registerServerReference} from "react-server-dom-webpack/server";\n'; localNames.forEach(function (exported, local) { if (localTypes.get(local) !== 'function') { // We first check if the export is a function and if so annotate it. newSrc += 'if (typeof ' + local + ' === "function") '; } - newSrc += 'Object.defineProperties(' + local + ',{'; - newSrc += '$$typeof: {value: Symbol.for("react.server.reference")},'; - newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + exported) + '},'; - newSrc += '$$bound: { value: null }'; - newSrc += '});\n'; + newSrc += 'registerServerReference(' + local + ','; + newSrc += JSON.stringify(url + '#' + exported); + newSrc += ');\n'; }); return newSrc; } @@ -313,13 +315,17 @@ async function transformClientModule( await parseExportNamesInto(body, names, url, loader); + if (names.length === 0) { + return ''; + } + let newSrc = - "const CLIENT_REFERENCE = Symbol.for('react.client.reference');\n"; + 'import {registerClientReference} from "react-server-dom-webpack/server";\n'; for (let i = 0; i < names.length; i++) { const name = names[i]; if (name === 'default') { newSrc += 'export default '; - newSrc += 'Object.defineProperties(function() {'; + newSrc += 'registerClientReference(function() {'; newSrc += 'throw new Error(' + JSON.stringify( @@ -331,7 +337,7 @@ async function transformClientModule( ');'; } else { newSrc += 'export const ' + name + ' = '; - newSrc += 'Object.defineProperties(function() {'; + newSrc += 'registerClientReference(function() {'; newSrc += 'throw new Error(' + JSON.stringify( @@ -341,10 +347,9 @@ async function transformClientModule( ) + ');'; } - newSrc += '},{'; - newSrc += '$$typeof: {value: CLIENT_REFERENCE},'; - newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + name) + '}'; - newSrc += '});\n'; + newSrc += '},'; + newSrc += JSON.stringify(url + '#' + name); + newSrc += ');\n'; } return newSrc; } diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js index 7040fdfeaf601..e3fa6bd7b8247 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js @@ -14,27 +14,11 @@ const url = require('url'); const Module = require('module'); module.exports = function register() { - const CLIENT_REFERENCE = Symbol.for('react.client.reference'); - const SERVER_REFERENCE = Symbol.for('react.server.reference'); + const Server: any = require('react-server-dom-webpack/server'); + const registerClientReference = Server.registerClientReference; + const registerServerReference = Server.registerServerReference; const PROMISE_PROTOTYPE = Promise.prototype; - // Patch bind on the server to ensure that this creates another - // bound server reference with the additional arguments. - const originalBind = Function.prototype.bind; - /*eslint-disable no-extend-native */ - Function.prototype.bind = (function bind(this: any, self: any) { - // $FlowFixMe[unsupported-syntax] - const newFn = originalBind.apply(this, arguments); - if (this.$$typeof === SERVER_REFERENCE) { - // $FlowFixMe[method-unbinding] - const args = Array.prototype.slice.call(arguments, 1); - newFn.$$typeof = SERVER_REFERENCE; - newFn.$$id = this.$$id; - newFn.$$bound = this.$$bound ? this.$$bound.concat(args) : args; - } - return newFn; - }: any); - const deepProxyHandlers = { get: function (target: Function, name: string, receiver: Proxy) { switch (name) { @@ -111,7 +95,7 @@ module.exports = function register() { // Something is conditionally checking which export to use. We'll pretend to be // an ESM compat module but then we'll check again on the client. const moduleId = target.$$id; - target.default = Object.defineProperties( + target.default = registerClientReference( (function () { throw new Error( `Attempted to call the default export of ${moduleId} from the server ` + @@ -120,13 +104,8 @@ module.exports = function register() { `Client Component.`, ); }: any), - { - $$typeof: {value: CLIENT_REFERENCE}, - // This a placeholder value that tells the client to conditionally use the - // whole object or just the default export. - $$id: {value: target.$$id + '#'}, - $$async: {value: target.$$async}, - }, + target.$$id + '#', + target.$$async, ); return true; case 'then': @@ -139,29 +118,26 @@ module.exports = function register() { // we should resolve that with a client reference that unwraps the Promise on // the client. - const clientReference = Object.defineProperties(({}: any), { - $$typeof: {value: CLIENT_REFERENCE}, - $$id: {value: target.$$id}, - $$async: {value: true}, - }); + const clientReference = registerClientReference( + ({}: any), + target.$$id, + true, + ); const proxy = new Proxy(clientReference, proxyHandlers); // Treat this as a resolved Promise for React's use() target.status = 'fulfilled'; target.value = proxy; - const then = (target.then = Object.defineProperties( + const then = (target.then = registerClientReference( (function then(resolve, reject: any) { // Expose to React. return Promise.resolve(resolve(proxy)); }: any), // If this is not used as a Promise but is treated as a reference to a `.then` // export then we should treat it as a reference to that name. - { - $$typeof: {value: CLIENT_REFERENCE}, - $$id: {value: target.$$id + '#then'}, - $$async: {value: false}, - }, + target.$$id + '#then', + false, )); return then; } else { @@ -173,7 +149,7 @@ module.exports = function register() { } let cachedReference = target[name]; if (!cachedReference) { - const reference = Object.defineProperties( + const reference = registerClientReference( (function () { throw new Error( // eslint-disable-next-line react-internal/safe-string-coercion @@ -184,13 +160,10 @@ module.exports = function register() { `only be rendered as a Component or passed to props of a Client Component.`, ); }: any), - { - name: {value: name}, - $$typeof: {value: CLIENT_REFERENCE}, - $$id: {value: target.$$id + '#' + name}, - $$async: {value: target.$$async}, - }, + target.$$id + '#' + name, + target.$$async, ); + Object.defineProperty(reference, 'name', {value: name}); cachedReference = target[name] = new Proxy( reference, deepProxyHandlers, @@ -264,12 +237,12 @@ module.exports = function register() { if (useClient) { const moduleId: string = (url.pathToFileURL(filename).href: any); - const clientReference = Object.defineProperties(({}: any), { - $$typeof: {value: CLIENT_REFERENCE}, + const clientReference = registerClientReference( + ({}: any), // Represents the whole Module object instead of a particular import. - $$id: {value: moduleId}, - $$async: {value: false}, - }); + moduleId, + false, + ); this.exports = new Proxy(clientReference, proxyHandlers); } @@ -284,23 +257,18 @@ module.exports = function register() { // reference. If there are any functions in the export. if (typeof exports === 'function') { // The module exports a function directly, - Object.defineProperties((exports: any), { - $$typeof: {value: SERVER_REFERENCE}, + registerServerReference( + (exports: any), // Represents the whole Module object instead of a particular import. - $$id: {value: moduleId}, - $$bound: {value: null}, - }); + moduleId, + ); } else { const keys = Object.keys(exports); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = exports[keys[i]]; if (typeof value === 'function') { - Object.defineProperties((value: any), { - $$typeof: {value: SERVER_REFERENCE}, - $$id: {value: moduleId + '#' + key}, - $$bound: {value: null}, - }); + registerServerReference((value: any), moduleId + '#' + key); } } } diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js new file mode 100644 index 0000000000000..7e7ec6f745ed9 --- /dev/null +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -0,0 +1,74 @@ +/** + * 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; +} + +export function registerClientReference( + proxyImplementation: any, + id: string, + async: boolean, +): ClientReference { + return Object.defineProperties(proxyImplementation, { + $$typeof: {value: CLIENT_REFERENCE_TAG}, + $$id: {value: id}, + $$async: {value: async}, + }); +} + +// $FlowFixMe[method-unbinding] +const FunctionBind = Function.prototype.bind; +// $FlowFixMe[method-unbinding] +const ArraySlice = Array.prototype.slice; +function bind(this: ServerReference) { + // $FlowFixMe[unsupported-syntax] + const newFn = FunctionBind.apply(this, arguments); + if (this.$$typeof === SERVER_REFERENCE_TAG) { + const args = ArraySlice.call(arguments, 1); + newFn.$$typeof = SERVER_REFERENCE_TAG; + newFn.$$id = this.$$id; + newFn.$$bound = this.$$bound ? this.$$bound.concat(args) : args; + } + return newFn; +} + +export function registerServerReference( + reference: ServerReference, + id: string, +): ServerReference { + return Object.defineProperties((reference: any), { + $$typeof: {value: SERVER_REFERENCE_TAG}, + $$id: {value: id}, + $$bound: {value: null}, + bind: {value: bind}, + }); +} diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 58ae58d9bd44b..b3c5484c282c0 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -447,7 +447,7 @@ const bundles = [ global: 'ReactFlightWebpackNodeRegister', minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: false, - externals: ['url', 'module'], + externals: ['url', 'module', 'react-server-dom-webpack/server'], }, /******* React Server DOM ESM Server *******/ From 9421cd874622c6c4d16d4000caa8dce5e9ede66c Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 30 Jun 2023 23:32:49 -0400 Subject: [PATCH 2/5] Move Proxy creation to createClientModuleProxy --- .../src/ReactFlightDOMServerBrowser.js | 1 + .../src/ReactFlightDOMServerEdge.js | 1 + .../src/ReactFlightDOMServerNode.js | 1 + .../src/ReactFlightWebpackNodeRegister.js | 172 +---------------- .../src/ReactFlightWebpackReferences.js | 176 ++++++++++++++++++ 5 files changed, 181 insertions(+), 170 deletions(-) diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js index 404253dfd162f..1042ab3f51caa 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js @@ -30,6 +30,7 @@ import {decodeAction} from 'react-server/src/ReactFlightActionServer'; export { registerServerReference, registerClientReference, + createClientModuleProxy, } from './ReactFlightWebpackReferences'; type Options = { diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js index 404253dfd162f..1042ab3f51caa 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js @@ -30,6 +30,7 @@ import {decodeAction} from 'react-server/src/ReactFlightActionServer'; export { registerServerReference, registerClientReference, + createClientModuleProxy, } from './ReactFlightWebpackReferences'; type Options = { diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js index 18d4b27dadb64..275312cceaf2b 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js @@ -41,6 +41,7 @@ import {decodeAction} from 'react-server/src/ReactFlightActionServer'; export { registerServerReference, registerClientReference, + createClientModuleProxy, } from './ReactFlightWebpackReferences'; function createDrainHandler(destination: Destination, request: Request) { diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js index e3fa6bd7b8247..73d64fa4bdf88 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js @@ -15,170 +15,8 @@ const Module = require('module'); module.exports = function register() { const Server: any = require('react-server-dom-webpack/server'); - const registerClientReference = Server.registerClientReference; const registerServerReference = Server.registerServerReference; - const PROMISE_PROTOTYPE = Promise.prototype; - - const deepProxyHandlers = { - get: function (target: Function, name: string, receiver: Proxy) { - switch (name) { - // These names are read by the Flight runtime if you end up using the exports object. - case '$$typeof': - // These names are a little too common. We should probably have a way to - // have the Flight runtime extract the inner target instead. - return target.$$typeof; - case '$$id': - return target.$$id; - case '$$async': - return target.$$async; - case 'name': - return target.name; - case 'displayName': - return undefined; - // We need to special case this because createElement reads it if we pass this - // reference. - case 'defaultProps': - return undefined; - // Avoid this attempting to be serialized. - case 'toJSON': - return undefined; - case Symbol.toPrimitive: - // $FlowFixMe[prop-missing] - return Object.prototype[Symbol.toPrimitive]; - case 'Provider': - throw new Error( - `Cannot render a Client Context Provider on the Server. ` + - `Instead, you can export a Client Component wrapper ` + - `that itself renders a Client Context Provider.`, - ); - } - // eslint-disable-next-line react-internal/safe-string-coercion - const expression = String(target.name) + '.' + String(name); - throw new Error( - `Cannot access ${expression} on the server. ` + - 'You cannot dot into a client module from a server component. ' + - 'You can only pass the imported name through.', - ); - }, - set: function () { - throw new Error('Cannot assign to a client module from a server module.'); - }, - }; - - const proxyHandlers = { - get: function ( - target: Function, - name: string, - receiver: Proxy, - ): $FlowFixMe { - switch (name) { - // These names are read by the Flight runtime if you end up using the exports object. - case '$$typeof': - return target.$$typeof; - case '$$id': - return target.$$id; - case '$$async': - return target.$$async; - case 'name': - return target.name; - // We need to special case this because createElement reads it if we pass this - // reference. - case 'defaultProps': - return undefined; - // Avoid this attempting to be serialized. - case 'toJSON': - return undefined; - case Symbol.toPrimitive: - // $FlowFixMe[prop-missing] - return Object.prototype[Symbol.toPrimitive]; - case '__esModule': - // Something is conditionally checking which export to use. We'll pretend to be - // an ESM compat module but then we'll check again on the client. - const moduleId = target.$$id; - target.default = registerClientReference( - (function () { - throw new Error( - `Attempted to call the default export of ${moduleId} from the server ` + - `but it's on the client. It's not possible to invoke a client function from ` + - `the server, it can only be rendered as a Component or passed to props of a ` + - `Client Component.`, - ); - }: any), - target.$$id + '#', - target.$$async, - ); - return true; - case 'then': - if (target.then) { - // Use a cached value - return target.then; - } - if (!target.$$async) { - // If this module is expected to return a Promise (such as an AsyncModule) then - // we should resolve that with a client reference that unwraps the Promise on - // the client. - - const clientReference = registerClientReference( - ({}: any), - target.$$id, - true, - ); - const proxy = new Proxy(clientReference, proxyHandlers); - - // Treat this as a resolved Promise for React's use() - target.status = 'fulfilled'; - target.value = proxy; - - const then = (target.then = registerClientReference( - (function then(resolve, reject: any) { - // Expose to React. - return Promise.resolve(resolve(proxy)); - }: any), - // If this is not used as a Promise but is treated as a reference to a `.then` - // export then we should treat it as a reference to that name. - target.$$id + '#then', - false, - )); - return then; - } else { - // Since typeof .then === 'function' is a feature test we'd continue recursing - // indefinitely if we return a function. Instead, we return an object reference - // if we check further. - return undefined; - } - } - let cachedReference = target[name]; - if (!cachedReference) { - const reference = registerClientReference( - (function () { - throw new Error( - // eslint-disable-next-line react-internal/safe-string-coercion - `Attempted to call ${String(name)}() from the server but ${String( - name, - )} is on the client. ` + - `It's not possible to invoke a client function from the server, it can ` + - `only be rendered as a Component or passed to props of a Client Component.`, - ); - }: any), - target.$$id + '#' + name, - target.$$async, - ); - Object.defineProperty(reference, 'name', {value: name}); - cachedReference = target[name] = new Proxy( - reference, - deepProxyHandlers, - ); - } - return cachedReference; - }, - getPrototypeOf(target: Function): Object { - // Pretend to be a Promise in case anyone asks. - return PROMISE_PROTOTYPE; - }, - set: function (): empty { - throw new Error('Cannot assign to a client module from a server module.'); - }, - }; + const createClientModuleProxy = Server.createClientModuleProxy; // $FlowFixMe[prop-missing] found when upgrading Flow const originalCompile = Module.prototype._compile; @@ -237,13 +75,7 @@ module.exports = function register() { if (useClient) { const moduleId: string = (url.pathToFileURL(filename).href: any); - const clientReference = registerClientReference( - ({}: any), - // Represents the whole Module object instead of a particular import. - moduleId, - false, - ); - this.exports = new Proxy(clientReference, proxyHandlers); + this.exports = createClientModuleProxy(moduleId); } if (useServer) { diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index 7e7ec6f745ed9..e0a0e89ef53e6 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -36,6 +36,13 @@ export function isServerReference(reference: Object): boolean { export function registerClientReference( proxyImplementation: any, id: string, +): ClientReference { + return registerClientReferenceImpl(proxyImplementation, id, false); +} + +function registerClientReferenceImpl( + proxyImplementation: any, + id: string, async: boolean, ): ClientReference { return Object.defineProperties(proxyImplementation, { @@ -72,3 +79,172 @@ export function registerServerReference( bind: {value: bind}, }); } + +const PROMISE_PROTOTYPE = Promise.prototype; + +const deepProxyHandlers = { + get: function (target: Function, name: string, receiver: Proxy) { + switch (name) { + // These names are read by the Flight runtime if you end up using the exports object. + case '$$typeof': + // These names are a little too common. We should probably have a way to + // have the Flight runtime extract the inner target instead. + return target.$$typeof; + case '$$id': + return target.$$id; + case '$$async': + return target.$$async; + case 'name': + return target.name; + case 'displayName': + return undefined; + // We need to special case this because createElement reads it if we pass this + // reference. + case 'defaultProps': + return undefined; + // Avoid this attempting to be serialized. + case 'toJSON': + return undefined; + case Symbol.toPrimitive: + // $FlowFixMe[prop-missing] + return Object.prototype[Symbol.toPrimitive]; + case 'Provider': + throw new Error( + `Cannot render a Client Context Provider on the Server. ` + + `Instead, you can export a Client Component wrapper ` + + `that itself renders a Client Context Provider.`, + ); + } + // eslint-disable-next-line react-internal/safe-string-coercion + const expression = String(target.name) + '.' + String(name); + throw new Error( + `Cannot access ${expression} on the server. ` + + 'You cannot dot into a client module from a server component. ' + + 'You can only pass the imported name through.', + ); + }, + set: function () { + throw new Error('Cannot assign to a client module from a server module.'); + }, +}; + +const proxyHandlers = { + get: function ( + target: Function, + name: string, + receiver: Proxy, + ): $FlowFixMe { + switch (name) { + // These names are read by the Flight runtime if you end up using the exports object. + case '$$typeof': + return target.$$typeof; + case '$$id': + return target.$$id; + case '$$async': + return target.$$async; + case 'name': + return target.name; + // We need to special case this because createElement reads it if we pass this + // reference. + case 'defaultProps': + return undefined; + // Avoid this attempting to be serialized. + case 'toJSON': + return undefined; + case Symbol.toPrimitive: + // $FlowFixMe[prop-missing] + return Object.prototype[Symbol.toPrimitive]; + case '__esModule': + // Something is conditionally checking which export to use. We'll pretend to be + // an ESM compat module but then we'll check again on the client. + const moduleId = target.$$id; + target.default = registerClientReferenceImpl( + (function () { + throw new Error( + `Attempted to call the default export of ${moduleId} from the server ` + + `but it's on the client. It's not possible to invoke a client function from ` + + `the server, it can only be rendered as a Component or passed to props of a ` + + `Client Component.`, + ); + }: any), + target.$$id + '#', + target.$$async, + ); + return true; + case 'then': + if (target.then) { + // Use a cached value + return target.then; + } + if (!target.$$async) { + // If this module is expected to return a Promise (such as an AsyncModule) then + // we should resolve that with a client reference that unwraps the Promise on + // the client. + + const clientReference: ClientReference = + registerClientReferenceImpl(({}: any), target.$$id, true); + const proxy = new Proxy(clientReference, proxyHandlers); + + // Treat this as a resolved Promise for React's use() + target.status = 'fulfilled'; + target.value = proxy; + + const then = (target.then = registerClientReferenceImpl( + (function then(resolve, reject: any) { + // Expose to React. + return Promise.resolve(resolve(proxy)); + }: any), + // If this is not used as a Promise but is treated as a reference to a `.then` + // export then we should treat it as a reference to that name. + target.$$id + '#then', + false, + )); + return then; + } else { + // Since typeof .then === 'function' is a feature test we'd continue recursing + // indefinitely if we return a function. Instead, we return an object reference + // if we check further. + return undefined; + } + } + let cachedReference = target[name]; + if (!cachedReference) { + const reference: ClientReference = registerClientReferenceImpl( + (function () { + throw new Error( + // eslint-disable-next-line react-internal/safe-string-coercion + `Attempted to call ${String(name)}() from the server but ${String( + name, + )} is on the client. ` + + `It's not possible to invoke a client function from the server, it can ` + + `only be rendered as a Component or passed to props of a Client Component.`, + ); + }: any), + target.$$id + '#' + name, + target.$$async, + ); + Object.defineProperty((reference: any), 'name', {value: name}); + cachedReference = target[name] = new Proxy(reference, deepProxyHandlers); + } + return cachedReference; + }, + getPrototypeOf(target: Function): Object { + // Pretend to be a Promise in case anyone asks. + return PROMISE_PROTOTYPE; + }, + set: function (): empty { + throw new Error('Cannot assign to a client module from a server module.'); + }, +}; + +export function createClientModuleProxy( + moduleId: string, +): ClientReference { + const clientReference: ClientReference = registerClientReferenceImpl( + ({}: any), + // Represents the whole Module object instead of a particular import. + moduleId, + false, + ); + return new Proxy(clientReference, proxyHandlers); +} From cf9a962fb58e013d86b97c12fbffb0dd957b1c37 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 1 Jul 2023 00:03:36 -0400 Subject: [PATCH 3/5] Simulate condition resolution to get the right root --- .../src/__tests__/ReactFlightDOM-test.js | 9 ++++++++- .../src/__tests__/ReactFlightDOMBrowser-test.js | 6 ++++++ .../src/__tests__/ReactFlightDOMEdge-test.js | 6 ++++++ .../src/__tests__/ReactFlightDOMForm-test.js | 4 ++++ .../src/__tests__/ReactFlightDOMNode-test.js | 6 ++++++ .../src/__tests__/ReactFlightDOMReply-test.js | 4 ++++ 6 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 8254a25c1a071..899dc639aa76f 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -36,6 +36,14 @@ let ErrorBoundary; describe('ReactFlightDOM', () => { beforeEach(() => { jest.resetModules(); + + // Simulate the condition resolution + jest.mock('react-server-dom-webpack/server', () => + require('react-server-dom-webpack/server.node.unbundled'), + ); + + ReactServerDOMClient = require('react-server-dom-webpack/client'); + act = require('internal-test-utils').act; const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; @@ -49,7 +57,6 @@ describe('ReactFlightDOM', () => { use = React.use; Suspense = React.Suspense; ReactDOMClient = require('react-dom/client'); - ReactServerDOMClient = require('react-server-dom-webpack/client'); ReactServerDOMServer = require('react-server-dom-webpack/server.node.unbundled'); ErrorBoundary = class extends React.Component { diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index b2cb66f5bb817..a1d67eba7d933 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -32,6 +32,12 @@ let use; describe('ReactFlightDOMBrowser', () => { beforeEach(() => { jest.resetModules(); + + // Simulate the condition resolution + jest.mock('react-server-dom-webpack/server', () => + require('react-server-dom-webpack/server.browser'), + ); + act = require('internal-test-utils').act; const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js index a08925af7bf9f..053f459cc2653 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js @@ -31,6 +31,12 @@ let use; describe('ReactFlightDOMEdge', () => { beforeEach(() => { jest.resetModules(); + + // Simulate the condition resolution + jest.mock('react-server-dom-webpack/server', () => + require('react-server-dom-webpack/server.edge'), + ); + const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; webpackMap = WebpackMock.webpackMap; diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js index e3bdba6de901a..e863dccee6c7b 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js @@ -32,6 +32,10 @@ let ReactServerDOMClient; describe('ReactFlightDOMForm', () => { beforeEach(() => { jest.resetModules(); + // Simulate the condition resolution + jest.mock('react-server-dom-webpack/server', () => + require('react-server-dom-webpack/server.edge'), + ); const WebpackMock = require('./utils/WebpackMock'); serverExports = WebpackMock.serverExports; webpackServerMap = WebpackMock.webpackServerMap; diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js index 4eed4c562152c..545c9517a901e 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js @@ -26,6 +26,12 @@ let use; describe('ReactFlightDOMNode', () => { beforeEach(() => { jest.resetModules(); + + // Simulate the condition resolution + jest.mock('react-server-dom-webpack/server', () => + require('react-server-dom-webpack/server.node'), + ); + const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; webpackMap = WebpackMock.webpackMap; diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js index 7392ccfe9606f..894f444640558 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js @@ -23,6 +23,10 @@ let ReactServerDOMClient; describe('ReactFlightDOMReply', () => { beforeEach(() => { jest.resetModules(); + // Simulate the condition resolution + jest.mock('react-server-dom-webpack/server', () => + require('react-server-dom-webpack/server.browser'), + ); const WebpackMock = require('./utils/WebpackMock'); // serverExports = WebpackMock.serverExports; webpackServerMap = WebpackMock.webpackServerMap; From ecdc6e16f9eac0cea8508d2978f1ab8c39989589 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 1 Jul 2023 00:41:11 -0400 Subject: [PATCH 4/5] Exclude from flow configs that can't access /server --- scripts/shared/inlinedHostConfigs.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index 2cb934d862e79..4b35976aeba29 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -145,6 +145,8 @@ module.exports = [ 'react-server-dom-webpack/server', 'react-server-dom-webpack/server.node', 'react-server-dom-webpack/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node + 'react-server-dom-webpack/node-register', + 'react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js', 'react-devtools', 'react-devtools-core', 'react-devtools-shell', From 097d0aa146ff9e4f82c38acf24e678fc0cba4d2e Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 1 Jul 2023 00:54:54 -0400 Subject: [PATCH 5/5] Pass id and export name as separate arguments --- .../src/ReactFlightESMNodeLoader.js | 8 ++++---- .../src/ReactFlightESMReferences.js | 8 ++++---- .../src/ReactFlightWebpackNodeLoader.js | 8 ++++---- .../src/ReactFlightWebpackNodeRegister.js | 5 +++-- .../src/ReactFlightWebpackReferences.js | 10 ++++++++-- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js index ef53556690ae7..2ee88a28c8abc 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js @@ -190,8 +190,8 @@ function transformServerModule( newSrc += 'if (typeof ' + local + ' === "function") '; } newSrc += 'registerServerReference(' + local + ','; - newSrc += JSON.stringify(url + '#' + exported); - newSrc += ');\n'; + newSrc += JSON.stringify(url) + ','; + newSrc += JSON.stringify(exported) + ');\n'; }); return newSrc; } @@ -348,8 +348,8 @@ async function transformClientModule( ');'; } newSrc += '},'; - newSrc += JSON.stringify(url + '#' + name); - newSrc += ');\n'; + newSrc += JSON.stringify(url) + ','; + newSrc += JSON.stringify(name) + ');\n'; } return newSrc; } diff --git a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js index bf7fbc2e134e5..57bbdd121cee8 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js @@ -35,12 +35,11 @@ export function isServerReference(reference: Object): boolean { export function registerClientReference( proxyImplementation: any, id: string, - async: boolean, + exportName: string, ): ClientReference { return Object.defineProperties(proxyImplementation, { $$typeof: {value: CLIENT_REFERENCE_TAG}, - $$id: {value: id}, - $$async: {value: async}, + $$id: {value: id + '#' + exportName}, }); } @@ -64,10 +63,11 @@ function bind(this: ServerReference) { export function registerServerReference( reference: ServerReference, id: string, + exportName: string, ): ServerReference { return Object.defineProperties((reference: any), { $$typeof: {value: SERVER_REFERENCE_TAG}, - $$id: {value: id}, + $$id: {value: id + '#' + exportName}, $$bound: {value: null}, bind: {value: bind}, }); diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js index 0406c1c96455e..58d577893fee0 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js @@ -190,8 +190,8 @@ function transformServerModule( newSrc += 'if (typeof ' + local + ' === "function") '; } newSrc += 'registerServerReference(' + local + ','; - newSrc += JSON.stringify(url + '#' + exported); - newSrc += ');\n'; + newSrc += JSON.stringify(url) + ','; + newSrc += JSON.stringify(exported) + ');\n'; }); return newSrc; } @@ -348,8 +348,8 @@ async function transformClientModule( ');'; } newSrc += '},'; - newSrc += JSON.stringify(url + '#' + name); - newSrc += ');\n'; + newSrc += JSON.stringify(url) + ','; + newSrc += JSON.stringify(name) + ');\n'; } return newSrc; } diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js index 73d64fa4bdf88..39598e0e85ec0 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js @@ -91,8 +91,9 @@ module.exports = function register() { // The module exports a function directly, registerServerReference( (exports: any), - // Represents the whole Module object instead of a particular import. moduleId, + // Represents the whole Module object instead of a particular import. + null, ); } else { const keys = Object.keys(exports); @@ -100,7 +101,7 @@ module.exports = function register() { const key = keys[i]; const value = exports[keys[i]]; if (typeof value === 'function') { - registerServerReference((value: any), moduleId + '#' + key); + registerServerReference((value: any), moduleId, key); } } } diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index e0a0e89ef53e6..9df0e43bd75e1 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -36,8 +36,13 @@ export function isServerReference(reference: Object): boolean { export function registerClientReference( proxyImplementation: any, id: string, + exportName: string, ): ClientReference { - return registerClientReferenceImpl(proxyImplementation, id, false); + return registerClientReferenceImpl( + proxyImplementation, + id + '#' + exportName, + false, + ); } function registerClientReferenceImpl( @@ -71,10 +76,11 @@ function bind(this: ServerReference) { export function registerServerReference( reference: ServerReference, id: string, + exportName: null | string, ): ServerReference { return Object.defineProperties((reference: any), { $$typeof: {value: SERVER_REFERENCE_TAG}, - $$id: {value: id}, + $$id: {value: exportName === null ? id : id + '#' + exportName}, $$bound: {value: null}, bind: {value: bind}, });