From efe1856f6b51d3486dcac53b38b9a1ca5012018d Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 14:44:43 +0200 Subject: [PATCH 01/10] WIP turbopack --- .../__tests__/ReactFlightTurbopackDOM-test.js | 109 ++++++++++++++++++ .../ReactFlightTurbopackDOMNode-test.js | 7 +- .../src/__tests__/utils/TurbopackMock.js | 58 +++++++++- ...ReactFlightServerConfigTurbopackBundler.js | 5 +- 4 files changed, 171 insertions(+), 8 deletions(-) diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js index 0cf2876fedb..551c1c2ba0c 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js +++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js @@ -29,6 +29,8 @@ let ReactServerDOMClient; let Suspense; let ReactServerScheduler; let reactServerAct; +let assertConsoleErrorDev; +let ErrorBoundary; describe('ReactFlightTurbopackDOM', () => { beforeEach(() => { @@ -57,12 +59,30 @@ describe('ReactFlightTurbopackDOM', () => { jest.resetModules(); __unmockReact(); act = require('internal-test-utils').act; + assertConsoleErrorDev = + require('internal-test-utils').assertConsoleErrorDev; Stream = require('stream'); React = require('react'); use = React.use; Suspense = React.Suspense; ReactDOMClient = require('react-dom/client'); ReactServerDOMClient = require('react-server-dom-turbopack/client'); + + ErrorBoundary = class extends React.Component { + state = {hasError: false, error: null}; + static getDerivedStateFromError(error) { + return { + hasError: true, + error, + }; + } + render() { + if (this.state.hasError) { + return this.props.fallback(this.state.error); + } + return this.props.children; + } + }; }); async function serverAct(callback) { @@ -220,4 +240,93 @@ describe('ReactFlightTurbopackDOM', () => { }); expect(container.innerHTML).toBe('

Async: Module

'); }); + + it('should unwrap async ESM module references', async () => { + const AsyncModule = Promise.resolve(function AsyncModule({text}) { + return 'Async: ' + text; + }); + + const AsyncModule2 = Promise.resolve({ + exportName: 'Module', + }); + + function Print({response}) { + return

{use(response)}

; + } + + function App({response}) { + return ( + Loading...}> + + + ); + } + + const AsyncModuleRef = await clientExports(AsyncModule, { + isESM: true, + }); + const AsyncModuleRef2 = await clientExports(AsyncModule2, { + isESM: true, + }); + + const {writable, readable} = getTestStream(); + const {pipe} = await serverAct(() => + ReactServerDOMServer.renderToPipeableStream( + , + turbopackMap, + ), + ); + pipe(writable); + const response = ReactServerDOMClient.createFromReadableStream(readable); + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(container.innerHTML).toBe('

Async: Module

'); + }); + + it('should error when a bundler uses async ESM modules with createClientModuleProxy', async () => { + const AsyncModule = Promise.resolve(function AsyncModule() { + return 'This should not be rendered'; + }); + + function Print({response}) { + return

{use(response)}

; + } + + function App({response}) { + return ( +

{e.message}

}> + Loading...}> + + +
+ ); + } + + const AsyncModuleRef = await clientExports(AsyncModule, { + isESM: true, + forceClientModuleProxy: true, + }); + + const {writable, readable} = getTestStream(); + const {pipe} = await serverAct(() => + ReactServerDOMServer.renderToPipeableStream( + , + turbopackMap, + ), + ); + pipe(writable); + const response = ReactServerDOMClient.createFromReadableStream(readable); + assertConsoleErrorDev(['TODO: error message']); + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(container.innerHTML).toBe('

TODO: error message

'); + }); }); diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js index a7b6ff75d57..32310332838 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js +++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js @@ -94,10 +94,9 @@ describe('ReactFlightTurbopackDOMNode', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = clientExports( - ClientComponent, - 'path/to/chunk.js', - ); + const ClientComponentOnTheClient = clientExports(ClientComponent, { + chunkUrl: 'path/to/chunk.js', + }); const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. diff --git a/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js b/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js index 5ed6fa53574..f35efb2351a 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js +++ b/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js @@ -23,6 +23,7 @@ global.__turbopack_require__ = function (id) { }; const Server = require('react-server-dom-turbopack/server'); +const registerClientReference = Server.registerClientReference; const registerServerReference = Server.registerServerReference; const createClientModuleProxy = Server.createClientModuleProxy; @@ -33,10 +34,17 @@ exports.moduleLoading = { prefix: '/prefix/', }; -exports.clientExports = function clientExports(moduleExports, chunkUrl) { +exports.clientExports = function clientExports( + moduleExports, + options?: { + chunkUrl?: string, + isESM?: boolean, + forceClientModuleProxy?: boolean, + } = {}, +) { const chunks = []; - if (chunkUrl !== undefined) { - chunks.push(chunkUrl); + if (options.chunkUrl !== undefined) { + chunks.push(options.chunkUrl); } const idx = '' + turbopackModuleIdx++; turbopackClientModules[idx] = moduleExports; @@ -55,6 +63,50 @@ exports.clientExports = function clientExports(moduleExports, chunkUrl) { }; } if (typeof moduleExports.then === 'function') { + if (options.isESM) { + return moduleExports.then( + asyncModuleExports => { + if (options.forceClientModuleProxy) { + turbopackClientMap[path] = { + id: idx, + chunks, + name: '*', + async: true, + }; + + return createClientModuleProxy(path); + } + + if (typeof asyncModuleExports === 'object') { + const references = {}; + + for (const name in asyncModuleExports) { + const id = path + '#' + name; + turbopackClientMap[path + '#' + name] = { + id: idx, + chunks, + name: name, + async: true, + }; + references[name] = registerClientReference(() => {}, id, name); + } + + return references; + } + + turbopackClientMap[path] = { + id: idx, + chunks, + name: '', + async: true, + }; + + return registerClientReference(() => {}, path, ''); + }, + () => {}, + ); + } + moduleExports.then( asyncModuleExports => { for (const name in asyncModuleExports) { diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js b/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js index d8224aff341..88348c7f3ff 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js @@ -71,7 +71,10 @@ export function resolveClientReferenceMetadata( ); } } - if (clientReference.$$async === true) { + if (clientReference.$$async === true && resolvedModuleData.async === true) { + throw new Error('TODO: error message'); + } + if (clientReference.$$async === true || resolvedModuleData.async === true) { return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1]; } else { return [resolvedModuleData.id, resolvedModuleData.chunks, name]; From 0a8d96d46fabbbabefd92f11a76177fb04c8c8d2 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 15:50:37 +0200 Subject: [PATCH 02/10] Use options param for `clientExports` in `WebpackMock` --- .../__tests__/ReactFlightDOMBrowser-test.js | 123 +++++++++++++----- .../src/__tests__/ReactFlightDOMNode-test.js | 16 +-- .../src/__tests__/utils/WebpackMock.js | 18 ++- 3 files changed, 107 insertions(+), 50 deletions(-) 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 fa1e6586256..4cf297ed17b 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -213,9 +213,15 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({children}) { return {children}; }, - '42', - '/test.js', - new Promise(resolve => (resolveClientComponentChunk = resolve)), + { + chunk: { + id: '42', + filename: '/test.js', + promise: new Promise( + resolve => (resolveClientComponentChunk = resolve), + ), + }, + }, ); function Server() { @@ -258,9 +264,15 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({value}) { return
{JSON.stringify(value)}
; }, - '42', - '/test.js', - new Promise(resolve => (resolveClientComponentChunk = resolve)), + { + chunk: { + id: '42', + filename: '/test.js', + promise: new Promise( + resolve => (resolveClientComponentChunk = resolve), + ), + }, + }, ); function Server({value}) { @@ -309,9 +321,15 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({value}) { return
{JSON.stringify(value)}
; }, - '42', - '/test.js', - new Promise(resolve => (resolveClientComponentChunk = resolve)), + { + chunk: { + id: '42', + filename: '/test.js', + promise: new Promise( + resolve => (resolveClientComponentChunk = resolve), + ), + }, + }, ); function Server({value}) { @@ -355,12 +373,15 @@ describe('ReactFlightDOMBrowser', () => { it('should resolve deduped objects that are themselves blocked', async () => { let resolveClientComponentChunk; - const Client = clientExports( - [4, 5], - '42', - '/test.js', - new Promise(resolve => (resolveClientComponentChunk = resolve)), - ); + const Client = clientExports([4, 5], { + chunk: { + id: '42', + filename: '/test.js', + promise: new Promise( + resolve => (resolveClientComponentChunk = resolve), + ), + }, + }); const shared = [1, 2, 3, Client]; @@ -407,9 +428,15 @@ describe('ReactFlightDOMBrowser', () => { function ClientOuter({children, value}) { return children; }, - '1', - '/outer.js', - new Promise(resolve => (resolveOuterClientComponentChunk = resolve)), + { + chunk: { + id: '1', + filename: '/outer.js', + promise: new Promise( + resolve => (resolveOuterClientComponentChunk = resolve), + ), + }, + }, ); function PassthroughServerComponent({children}) { @@ -420,9 +447,15 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({children}) { return JSON.stringify(children); }, - '2', - '/inner.js', - new Promise(resolve => (resolveInnerClientComponentChunk = resolve)), + { + chunk: { + id: '2', + filename: '/inner.js', + promise: new Promise( + resolve => (resolveInnerClientComponentChunk = resolve), + ), + }, + }, ); const value = {}; @@ -475,18 +508,30 @@ describe('ReactFlightDOMBrowser', () => { function FooClient({children}) { return JSON.stringify(children); }, - '1', - '/foo.js', - new Promise(resolve => (resolveFooClientComponentChunk = resolve)), + { + chunk: { + id: '1', + filename: '/foo.js', + promise: new Promise( + resolve => (resolveFooClientComponentChunk = resolve), + ), + }, + }, ); const BarClient = clientExports( function BarClient() { return 'not used'; }, - '2', - '/bar.js', - new Promise(resolve => (resolveBarClientComponentChunk = resolve)), + { + chunk: { + id: '2', + filename: '/bar.js', + promise: new Promise( + resolve => (resolveBarClientComponentChunk = resolve), + ), + }, + }, ); const shared = {foo: 1}; @@ -539,9 +584,15 @@ describe('ReactFlightDOMBrowser', () => { function Foo({children, item}) { return children; }, - '1', - '/foo.js', - new Promise(resolve => (resolveFooClientComponentChunk = resolve)), + { + chunk: { + id: '1', + filename: '/foo.js', + promise: new Promise( + resolve => (resolveFooClientComponentChunk = resolve), + ), + }, + }, ); const shared =
; @@ -590,9 +641,15 @@ describe('ReactFlightDOMBrowser', () => { function Foo({children, item}) { return children; }, - '1', - '/foo.js', - new Promise(resolve => (resolveFooClientComponentChunk = resolve)), + { + chunk: { + id: '1', + filename: '/foo.js', + promise: new Promise( + resolve => (resolveFooClientComponentChunk = resolve), + ), + }, + }, ); const shared =
; 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 f2dca4a45c7..4f879eff4a1 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js @@ -111,11 +111,9 @@ describe('ReactFlightDOMNode', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = clientExports( - ClientComponent, - 123, - 'path/to/chunk.js', - ); + const ClientComponentOnTheClient = clientExports(ClientComponent, { + chunk: {id: 123, filename: 'path/to/chunk.js'}, + }); const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. @@ -236,11 +234,9 @@ describe('ReactFlightDOMNode', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = clientExports( - ClientComponent, - 123, - 'path/to/chunk.js', - ); + const ClientComponentOnTheClient = clientExports(ClientComponent, { + chunk: {id: 123, filename: 'path/to/chunk.js'}, + }); const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. diff --git a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js index 4527118c1de..96dc0c765fb 100644 --- a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js +++ b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js @@ -67,16 +67,20 @@ exports.clientModuleError = function clientModuleError(moduleError) { exports.clientExports = function clientExports( moduleExports, - chunkId, - chunkFilename, - blockOnChunk, + options?: { + chunk?: { + id: string | number, + filename: string, + promise?: Promise, + }, + } = {}, ) { const chunks = []; - if (chunkId) { - chunks.push(chunkId, chunkFilename); + if (options.chunk) { + chunks.push(options.chunk.id, options.chunk.filename); - if (blockOnChunk) { - webpackChunkMap[chunkId] = blockOnChunk; + if (options.chunk.promise) { + webpackChunkMap[options.chunk.id] = options.chunk.promise; } } const idx = '' + webpackModuleIdx++; From f69f2f95a1bfae87b357fb6312b1cfaafbb67cfb Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 16:06:40 +0200 Subject: [PATCH 03/10] WIP webpack --- .../src/__tests__/ReactFlightDOM-test.js | 85 +++++++++++++++++++ .../src/__tests__/utils/WebpackMock.js | 54 +++++++++++- .../ReactFlightServerConfigWebpackBundler.js | 5 +- 3 files changed, 140 insertions(+), 4 deletions(-) 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 41fc0bfd410..866a8984a9c 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -583,6 +583,91 @@ describe('ReactFlightDOM', () => { expect(container.innerHTML).toBe('

Async Text

'); }); + it('should unwrap async ESM module references', async () => { + const AsyncModule = Promise.resolve(function AsyncModule({text}) { + return 'Async: ' + text; + }); + + const AsyncModule2 = Promise.resolve({ + exportName: 'Module', + }); + + function Print({response}) { + return

{use(response)}

; + } + + function App({response}) { + return ( + Loading...}> + + + ); + } + + const AsyncModuleRef = await clientExports(AsyncModule, {isESM: true}); + const AsyncModuleRef2 = await clientExports(AsyncModule2, {isESM: true}); + + const {writable, readable} = getTestStream(); + const {pipe} = await serverAct(() => + ReactServerDOMServer.renderToPipeableStream( + , + webpackMap, + ), + ); + pipe(writable); + const response = ReactServerDOMClient.createFromReadableStream(readable); + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(container.innerHTML).toBe('

Async: Module

'); + }); + + it('should error when a bundler uses async ESM modules with createClientModuleProxy', async () => { + const AsyncModule = Promise.resolve(function AsyncModule() { + return 'This should not be rendered'; + }); + + function Print({response}) { + return

{use(response)}

; + } + + function App({response}) { + return ( +

{e.message}

}> + Loading...}> + + +
+ ); + } + + const AsyncModuleRef = await clientExports(AsyncModule, { + isESM: true, + forceClientModuleProxy: true, + }); + + const {writable, readable} = getTestStream(); + const {pipe} = await serverAct(() => + ReactServerDOMServer.renderToPipeableStream( + , + webpackMap, + ), + ); + pipe(writable); + const response = ReactServerDOMClient.createFromReadableStream(readable); + assertConsoleErrorDev(['TODO: error message']); + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(container.innerHTML).toBe('

TODO: error message

'); + }); + it('should be able to import a name called "then"', async () => { const thenExports = { then: function then() { diff --git a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js index 96dc0c765fb..a5974e5c11e 100644 --- a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js +++ b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js @@ -44,6 +44,10 @@ if (previousCompile === nodeCompile) { Module.prototype._compile = previousCompile; +const Server = require('react-server-dom-webpack/server'); +const registerClientReference = Server.registerClientReference; +const createClientModuleProxy = Server.createClientModuleProxy; + exports.webpackMap = webpackClientMap; exports.webpackModules = webpackClientModules; exports.webpackServerMap = webpackServerMap; @@ -73,6 +77,8 @@ exports.clientExports = function clientExports( filename: string, promise?: Promise, }, + isESM?: boolean, + forceClientModuleProxy?: boolean, } = {}, ) { const chunks = []; @@ -100,6 +106,50 @@ exports.clientExports = function clientExports( }; } if (typeof moduleExports.then === 'function') { + if (options.isESM) { + return moduleExports.then( + asyncModuleExports => { + if (options.forceClientModuleProxy) { + webpackClientMap[path] = { + id: idx, + chunks, + name: '*', + async: true, + }; + + return createClientModuleProxy(path); + } + + if (typeof asyncModuleExports === 'object') { + const references = {}; + + for (const name in asyncModuleExports) { + const id = path + '#' + name; + webpackClientMap[path + '#' + name] = { + id: idx, + chunks, + name: name, + async: true, + }; + references[name] = registerClientReference(() => {}, id, name); + } + + return references; + } + + webpackClientMap[path] = { + id: idx, + chunks, + name: '', + async: true, + }; + + return registerClientReference(() => {}, path, ''); + }, + () => {}, + ); + } + moduleExports.then( asyncModuleExports => { for (const name in asyncModuleExports) { @@ -125,9 +175,7 @@ exports.clientExports = function clientExports( name: 's', }; } - const mod = new Module(); - nodeCompile.call(mod, '"use client"', idx); - return mod.exports; + return createClientModuleProxy(path); }; // This tests server to server references. There's another case of client to server references. diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js b/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js index d29516ff946..4feb673c6df 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js @@ -71,7 +71,10 @@ export function resolveClientReferenceMetadata( ); } } - if (clientReference.$$async === true) { + if (clientReference.$$async === true && resolvedModuleData.async === true) { + throw new Error('TODO: error message'); + } + if (clientReference.$$async === true || resolvedModuleData.async === true) { return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1]; } else { return [resolvedModuleData.id, resolvedModuleData.chunks, name]; From 7dc6a10fc7b7faf7cf9cb4cc988f5077787117a6 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 20:27:06 +0200 Subject: [PATCH 04/10] Finalize error message --- .../__tests__/ReactFlightTurbopackDOM-test.js | 26 ++++++++++++++----- ...ReactFlightServerConfigTurbopackBundler.js | 11 +++++--- .../src/__tests__/ReactFlightDOM-test.js | 23 +++++++++++++--- .../ReactFlightServerConfigWebpackBundler.js | 11 +++++--- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js index 551c1c2ba0c..cc066514a54 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js +++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js @@ -29,7 +29,6 @@ let ReactServerDOMClient; let Suspense; let ReactServerScheduler; let reactServerAct; -let assertConsoleErrorDev; let ErrorBoundary; describe('ReactFlightTurbopackDOM', () => { @@ -59,8 +58,6 @@ describe('ReactFlightTurbopackDOM', () => { jest.resetModules(); __unmockReact(); act = require('internal-test-utils').act; - assertConsoleErrorDev = - require('internal-test-utils').assertConsoleErrorDev; Stream = require('stream'); React = require('react'); use = React.use; @@ -298,7 +295,13 @@ describe('ReactFlightTurbopackDOM', () => { function App({response}) { return ( -

{e.message}

}> + ( +

+ {__DEV__ ? error.message + ' + ' : null} + {error.digest} +

+ )}> Loading...}> @@ -316,17 +319,28 @@ describe('ReactFlightTurbopackDOM', () => { ReactServerDOMServer.renderToPipeableStream( , turbopackMap, + { + onError(error) { + return __DEV__ ? 'a dev digest' : `digest(${error.message})`; + }, + }, ), ); pipe(writable); const response = ReactServerDOMClient.createFromReadableStream(readable); - assertConsoleErrorDev(['TODO: error message']); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(); }); - expect(container.innerHTML).toBe('

TODO: error message

'); + + const errorMessage = `The module "${Object.keys(turbopackMap).at(0)}" is marked as an async ESM module but was loaded as a CJS proxy.This is probably a bug in the React Server Components bundler.`; + + expect(container.innerHTML).toBe( + __DEV__ + ? `

${errorMessage} + a dev digest

` + : `

digest(${errorMessage})

`, + ); }); }); diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js b/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js index 88348c7f3ff..4aa396761cd 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js @@ -71,10 +71,15 @@ export function resolveClientReferenceMetadata( ); } } - if (clientReference.$$async === true && resolvedModuleData.async === true) { - throw new Error('TODO: error message'); + if (resolvedModuleData.async === true && clientReference.$$async === true) { + throw new Error( + 'The module "' + + modulePath + + '" is marked as an async ESM module but was loaded as a CJS proxy.' + + 'This is probably a bug in the React Server Components bundler.', + ); } - if (clientReference.$$async === true || resolvedModuleData.async === true) { + if (resolvedModuleData.async === true || clientReference.$$async === true) { return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1]; } else { return [resolvedModuleData.id, resolvedModuleData.chunks, name]; 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 866a8984a9c..e04e9ad2f52 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -636,7 +636,13 @@ describe('ReactFlightDOM', () => { function App({response}) { return ( -

{e.message}

}> + ( +

+ {__DEV__ ? error.message + ' + ' : null} + {error.digest} +

+ )}> Loading...}> @@ -654,18 +660,29 @@ describe('ReactFlightDOM', () => { ReactServerDOMServer.renderToPipeableStream( , webpackMap, + { + onError(error) { + return __DEV__ ? 'a dev digest' : `digest(${error.message})`; + }, + }, ), ); pipe(writable); const response = ReactServerDOMClient.createFromReadableStream(readable); - assertConsoleErrorDev(['TODO: error message']); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(); }); - expect(container.innerHTML).toBe('

TODO: error message

'); + + const errorMessage = `The module "${Object.keys(webpackMap).at(0)}" is marked as an async ESM module but was loaded as a CJS proxy.This is probably a bug in the React Server Components bundler.`; + + expect(container.innerHTML).toBe( + __DEV__ + ? `

${errorMessage} + a dev digest

` + : `

digest(${errorMessage})

`, + ); }); it('should be able to import a name called "then"', async () => { diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js b/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js index 4feb673c6df..1d7f286d4f5 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js @@ -71,10 +71,15 @@ export function resolveClientReferenceMetadata( ); } } - if (clientReference.$$async === true && resolvedModuleData.async === true) { - throw new Error('TODO: error message'); + if (resolvedModuleData.async === true && clientReference.$$async === true) { + throw new Error( + 'The module "' + + modulePath + + '" is marked as an async ESM module but was loaded as a CJS proxy.' + + 'This is probably a bug in the React Server Components bundler.', + ); } - if (clientReference.$$async === true || resolvedModuleData.async === true) { + if (resolvedModuleData.async === true || clientReference.$$async === true) { return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1]; } else { return [resolvedModuleData.id, resolvedModuleData.chunks, name]; From 4ea5db077445270e0f44e72a033a486aba67d7a6 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 20:48:02 +0200 Subject: [PATCH 05/10] Revert "Use options param for `clientExports` in `WebpackMock`" This reverts commit 2ad3ebdeaef5ac9f687d4da13073ee23b0d1ab51. --- .../__tests__/ReactFlightDOMBrowser-test.js | 123 +++++------------- .../src/__tests__/ReactFlightDOMNode-test.js | 16 ++- .../src/__tests__/utils/WebpackMock.js | 68 ++-------- 3 files changed, 53 insertions(+), 154 deletions(-) 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 4cf297ed17b..fa1e6586256 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -213,15 +213,9 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({children}) { return {children}; }, - { - chunk: { - id: '42', - filename: '/test.js', - promise: new Promise( - resolve => (resolveClientComponentChunk = resolve), - ), - }, - }, + '42', + '/test.js', + new Promise(resolve => (resolveClientComponentChunk = resolve)), ); function Server() { @@ -264,15 +258,9 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({value}) { return
{JSON.stringify(value)}
; }, - { - chunk: { - id: '42', - filename: '/test.js', - promise: new Promise( - resolve => (resolveClientComponentChunk = resolve), - ), - }, - }, + '42', + '/test.js', + new Promise(resolve => (resolveClientComponentChunk = resolve)), ); function Server({value}) { @@ -321,15 +309,9 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({value}) { return
{JSON.stringify(value)}
; }, - { - chunk: { - id: '42', - filename: '/test.js', - promise: new Promise( - resolve => (resolveClientComponentChunk = resolve), - ), - }, - }, + '42', + '/test.js', + new Promise(resolve => (resolveClientComponentChunk = resolve)), ); function Server({value}) { @@ -373,15 +355,12 @@ describe('ReactFlightDOMBrowser', () => { it('should resolve deduped objects that are themselves blocked', async () => { let resolveClientComponentChunk; - const Client = clientExports([4, 5], { - chunk: { - id: '42', - filename: '/test.js', - promise: new Promise( - resolve => (resolveClientComponentChunk = resolve), - ), - }, - }); + const Client = clientExports( + [4, 5], + '42', + '/test.js', + new Promise(resolve => (resolveClientComponentChunk = resolve)), + ); const shared = [1, 2, 3, Client]; @@ -428,15 +407,9 @@ describe('ReactFlightDOMBrowser', () => { function ClientOuter({children, value}) { return children; }, - { - chunk: { - id: '1', - filename: '/outer.js', - promise: new Promise( - resolve => (resolveOuterClientComponentChunk = resolve), - ), - }, - }, + '1', + '/outer.js', + new Promise(resolve => (resolveOuterClientComponentChunk = resolve)), ); function PassthroughServerComponent({children}) { @@ -447,15 +420,9 @@ describe('ReactFlightDOMBrowser', () => { function ClientInner({children}) { return JSON.stringify(children); }, - { - chunk: { - id: '2', - filename: '/inner.js', - promise: new Promise( - resolve => (resolveInnerClientComponentChunk = resolve), - ), - }, - }, + '2', + '/inner.js', + new Promise(resolve => (resolveInnerClientComponentChunk = resolve)), ); const value = {}; @@ -508,30 +475,18 @@ describe('ReactFlightDOMBrowser', () => { function FooClient({children}) { return JSON.stringify(children); }, - { - chunk: { - id: '1', - filename: '/foo.js', - promise: new Promise( - resolve => (resolveFooClientComponentChunk = resolve), - ), - }, - }, + '1', + '/foo.js', + new Promise(resolve => (resolveFooClientComponentChunk = resolve)), ); const BarClient = clientExports( function BarClient() { return 'not used'; }, - { - chunk: { - id: '2', - filename: '/bar.js', - promise: new Promise( - resolve => (resolveBarClientComponentChunk = resolve), - ), - }, - }, + '2', + '/bar.js', + new Promise(resolve => (resolveBarClientComponentChunk = resolve)), ); const shared = {foo: 1}; @@ -584,15 +539,9 @@ describe('ReactFlightDOMBrowser', () => { function Foo({children, item}) { return children; }, - { - chunk: { - id: '1', - filename: '/foo.js', - promise: new Promise( - resolve => (resolveFooClientComponentChunk = resolve), - ), - }, - }, + '1', + '/foo.js', + new Promise(resolve => (resolveFooClientComponentChunk = resolve)), ); const shared =
; @@ -641,15 +590,9 @@ describe('ReactFlightDOMBrowser', () => { function Foo({children, item}) { return children; }, - { - chunk: { - id: '1', - filename: '/foo.js', - promise: new Promise( - resolve => (resolveFooClientComponentChunk = resolve), - ), - }, - }, + '1', + '/foo.js', + new Promise(resolve => (resolveFooClientComponentChunk = resolve)), ); const shared =
; 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 4f879eff4a1..f2dca4a45c7 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js @@ -111,9 +111,11 @@ describe('ReactFlightDOMNode', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = clientExports(ClientComponent, { - chunk: {id: 123, filename: 'path/to/chunk.js'}, - }); + const ClientComponentOnTheClient = clientExports( + ClientComponent, + 123, + 'path/to/chunk.js', + ); const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. @@ -234,9 +236,11 @@ describe('ReactFlightDOMNode', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = clientExports(ClientComponent, { - chunk: {id: 123, filename: 'path/to/chunk.js'}, - }); + const ClientComponentOnTheClient = clientExports( + ClientComponent, + 123, + 'path/to/chunk.js', + ); const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. diff --git a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js index a5974e5c11e..f8aa7a16208 100644 --- a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js +++ b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js @@ -71,22 +71,16 @@ exports.clientModuleError = function clientModuleError(moduleError) { exports.clientExports = function clientExports( moduleExports, - options?: { - chunk?: { - id: string | number, - filename: string, - promise?: Promise, - }, - isESM?: boolean, - forceClientModuleProxy?: boolean, - } = {}, + chunkId, + chunkFilename, + blockOnChunk, ) { const chunks = []; - if (options.chunk) { - chunks.push(options.chunk.id, options.chunk.filename); + if (chunkId) { + chunks.push(chunkId, chunkFilename); - if (options.chunk.promise) { - webpackChunkMap[options.chunk.id] = options.chunk.promise; + if (blockOnChunk) { + webpackChunkMap[chunkId] = blockOnChunk; } } const idx = '' + webpackModuleIdx++; @@ -106,50 +100,6 @@ exports.clientExports = function clientExports( }; } if (typeof moduleExports.then === 'function') { - if (options.isESM) { - return moduleExports.then( - asyncModuleExports => { - if (options.forceClientModuleProxy) { - webpackClientMap[path] = { - id: idx, - chunks, - name: '*', - async: true, - }; - - return createClientModuleProxy(path); - } - - if (typeof asyncModuleExports === 'object') { - const references = {}; - - for (const name in asyncModuleExports) { - const id = path + '#' + name; - webpackClientMap[path + '#' + name] = { - id: idx, - chunks, - name: name, - async: true, - }; - references[name] = registerClientReference(() => {}, id, name); - } - - return references; - } - - webpackClientMap[path] = { - id: idx, - chunks, - name: '', - async: true, - }; - - return registerClientReference(() => {}, path, ''); - }, - () => {}, - ); - } - moduleExports.then( asyncModuleExports => { for (const name in asyncModuleExports) { @@ -175,7 +125,9 @@ exports.clientExports = function clientExports( name: 's', }; } - return createClientModuleProxy(path); + const mod = new Module(); + nodeCompile.call(mod, '"use client"', idx); + return mod.exports; }; // This tests server to server references. There's another case of client to server references. From 3e4ee4a9093a64525ab80f9a2789cedcfa3b0589 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 20:52:44 +0200 Subject: [PATCH 06/10] Use `clientExportsESM` for webpack --- .../src/__tests__/ReactFlightDOM-test.js | 9 +-- .../src/__tests__/utils/WebpackMock.js | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) 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 e04e9ad2f52..f89b2fe46b2 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -21,6 +21,7 @@ global.TextDecoder = require('util').TextDecoder; let act; let use; let clientExports; +let clientExportsESM; let clientModuleError; let webpackMap; let Stream; @@ -68,6 +69,7 @@ describe('ReactFlightDOM', () => { } const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; + clientExportsESM = WebpackMock.clientExportsESM; clientModuleError = WebpackMock.clientModuleError; webpackMap = WebpackMock.webpackMap; @@ -604,8 +606,8 @@ describe('ReactFlightDOM', () => { ); } - const AsyncModuleRef = await clientExports(AsyncModule, {isESM: true}); - const AsyncModuleRef2 = await clientExports(AsyncModule2, {isESM: true}); + const AsyncModuleRef = await clientExportsESM(AsyncModule); + const AsyncModuleRef2 = await clientExportsESM(AsyncModule2); const {writable, readable} = getTestStream(); const {pipe} = await serverAct(() => @@ -650,8 +652,7 @@ describe('ReactFlightDOM', () => { ); } - const AsyncModuleRef = await clientExports(AsyncModule, { - isESM: true, + const AsyncModuleRef = await clientExportsESM(AsyncModule, { forceClientModuleProxy: true, }); diff --git a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js index f8aa7a16208..d17eb42a933 100644 --- a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js +++ b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js @@ -130,6 +130,61 @@ exports.clientExports = function clientExports( return mod.exports; }; +exports.clientExportsESM = function clientExportsESM( + moduleExports, + options?: {forceClientModuleProxy?: boolean} = {}, +) { + const chunks = []; + const idx = '' + webpackModuleIdx++; + webpackClientModules[idx] = moduleExports; + const path = url.pathToFileURL(idx).href; + + const createClientReferencesForExports = ({exports, async}) => { + webpackClientMap[path] = { + id: idx, + chunks, + name: '*', + async: true, + }; + + if (options.forceClientModuleProxy) { + return createClientModuleProxy(path); + } + + if (typeof exports === 'object') { + const references = {}; + + for (const name in exports) { + const id = path + '#' + name; + webpackClientMap[path + '#' + name] = { + id: idx, + chunks, + name: name, + async, + }; + references[name] = registerClientReference(() => {}, id, name); + } + + return references; + } + + return registerClientReference(() => {}, path, '*'); + }; + + if ( + moduleExports && + typeof moduleExports === 'object' && + typeof moduleExports.then === 'function' + ) { + return moduleExports.then( + exports => createClientReferencesForExports({exports, async: true}), + () => {}, + ); + } + + return createClientReferencesForExports({exports}); +}; + // This tests server to server references. There's another case of client to server references. exports.serverExports = function serverExports(moduleExports, blockOnChunk) { const idx = '' + webpackModuleIdx++; From ffa818741154c1b1e27978e1819ddc8599a62e2c Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 20:56:39 +0200 Subject: [PATCH 07/10] Use `clientExportsESM` for turbopack --- .../__tests__/ReactFlightTurbopackDOM-test.js | 13 +- .../ReactFlightTurbopackDOMNode-test.js | 7 +- .../src/__tests__/utils/TurbopackMock.js | 112 +++++++++--------- 3 files changed, 67 insertions(+), 65 deletions(-) diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js index cc066514a54..694be66faf1 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js +++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js @@ -20,6 +20,7 @@ global.TextDecoder = require('util').TextDecoder; let act; let use; let clientExports; +let clientExportsESM; let turbopackMap; let Stream; let React; @@ -50,6 +51,7 @@ describe('ReactFlightTurbopackDOM', () => { const TurbopackMock = require('./utils/TurbopackMock'); clientExports = TurbopackMock.clientExports; + clientExportsESM = TurbopackMock.clientExportsESM; turbopackMap = TurbopackMock.turbopackMap; ReactServerDOMServer = require('react-server-dom-turbopack/server'); @@ -259,12 +261,8 @@ describe('ReactFlightTurbopackDOM', () => { ); } - const AsyncModuleRef = await clientExports(AsyncModule, { - isESM: true, - }); - const AsyncModuleRef2 = await clientExports(AsyncModule2, { - isESM: true, - }); + const AsyncModuleRef = await clientExportsESM(AsyncModule); + const AsyncModuleRef2 = await clientExportsESM(AsyncModule2); const {writable, readable} = getTestStream(); const {pipe} = await serverAct(() => @@ -309,8 +307,7 @@ describe('ReactFlightTurbopackDOM', () => { ); } - const AsyncModuleRef = await clientExports(AsyncModule, { - isESM: true, + const AsyncModuleRef = await clientExportsESM(AsyncModule, { forceClientModuleProxy: true, }); diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js index 32310332838..a7b6ff75d57 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js +++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js @@ -94,9 +94,10 @@ describe('ReactFlightTurbopackDOMNode', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = clientExports(ClientComponent, { - chunkUrl: 'path/to/chunk.js', - }); + const ClientComponentOnTheClient = clientExports( + ClientComponent, + 'path/to/chunk.js', + ); const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. diff --git a/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js b/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js index f35efb2351a..ee0f00c942e 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js +++ b/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js @@ -34,17 +34,10 @@ exports.moduleLoading = { prefix: '/prefix/', }; -exports.clientExports = function clientExports( - moduleExports, - options?: { - chunkUrl?: string, - isESM?: boolean, - forceClientModuleProxy?: boolean, - } = {}, -) { +exports.clientExports = function clientExports(moduleExports, chunkUrl) { const chunks = []; - if (options.chunkUrl !== undefined) { - chunks.push(options.chunkUrl); + if (chunkUrl !== undefined) { + chunks.push(chunkUrl); } const idx = '' + turbopackModuleIdx++; turbopackClientModules[idx] = moduleExports; @@ -63,50 +56,6 @@ exports.clientExports = function clientExports( }; } if (typeof moduleExports.then === 'function') { - if (options.isESM) { - return moduleExports.then( - asyncModuleExports => { - if (options.forceClientModuleProxy) { - turbopackClientMap[path] = { - id: idx, - chunks, - name: '*', - async: true, - }; - - return createClientModuleProxy(path); - } - - if (typeof asyncModuleExports === 'object') { - const references = {}; - - for (const name in asyncModuleExports) { - const id = path + '#' + name; - turbopackClientMap[path + '#' + name] = { - id: idx, - chunks, - name: name, - async: true, - }; - references[name] = registerClientReference(() => {}, id, name); - } - - return references; - } - - turbopackClientMap[path] = { - id: idx, - chunks, - name: '', - async: true, - }; - - return registerClientReference(() => {}, path, ''); - }, - () => {}, - ); - } - moduleExports.then( asyncModuleExports => { for (const name in asyncModuleExports) { @@ -135,6 +84,61 @@ exports.clientExports = function clientExports( return createClientModuleProxy(path); }; +exports.clientExportsESM = function clientExportsESM( + moduleExports, + options?: {forceClientModuleProxy?: boolean} = {}, +) { + const chunks = []; + const idx = '' + turbopackModuleIdx++; + turbopackClientModules[idx] = moduleExports; + const path = url.pathToFileURL(idx).href; + + const createClientReferencesForExports = ({exports, async}) => { + turbopackClientMap[path] = { + id: idx, + chunks, + name: '*', + async: true, + }; + + if (options.forceClientModuleProxy) { + return createClientModuleProxy(path); + } + + if (typeof exports === 'object') { + const references = {}; + + for (const name in exports) { + const id = path + '#' + name; + turbopackClientMap[path + '#' + name] = { + id: idx, + chunks, + name: name, + async, + }; + references[name] = registerClientReference(() => {}, id, name); + } + + return references; + } + + return registerClientReference(() => {}, path, '*'); + }; + + if ( + moduleExports && + typeof moduleExports === 'object' && + typeof moduleExports.then === 'function' + ) { + return moduleExports.then( + exports => createClientReferencesForExports({exports, async: true}), + () => {}, + ); + } + + return createClientReferencesForExports({exports}); +}; + // This tests server to server references. There's another case of client to server references. exports.serverExports = function serverExports(moduleExports) { const idx = '' + turbopackModuleIdx++; From 6cb770b59f018017fd9796bd20522afd61893bd9 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 21:23:00 +0200 Subject: [PATCH 08/10] Add missing space to error message --- .../src/__tests__/ReactFlightTurbopackDOM-test.js | 2 +- .../src/server/ReactFlightServerConfigTurbopackBundler.js | 2 +- .../src/__tests__/ReactFlightDOM-test.js | 2 +- .../src/server/ReactFlightServerConfigWebpackBundler.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js index 694be66faf1..d4fc59b8262 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js +++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js @@ -332,7 +332,7 @@ describe('ReactFlightTurbopackDOM', () => { root.render(); }); - const errorMessage = `The module "${Object.keys(turbopackMap).at(0)}" is marked as an async ESM module but was loaded as a CJS proxy.This is probably a bug in the React Server Components bundler.`; + const errorMessage = `The module "${Object.keys(turbopackMap).at(0)}" is marked as an async ESM module but was loaded as a CJS proxy. This is probably a bug in the React Server Components bundler.`; expect(container.innerHTML).toBe( __DEV__ diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js b/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js index 4aa396761cd..219391f8f81 100644 --- a/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js +++ b/packages/react-server-dom-turbopack/src/server/ReactFlightServerConfigTurbopackBundler.js @@ -75,7 +75,7 @@ export function resolveClientReferenceMetadata( throw new Error( 'The module "' + modulePath + - '" is marked as an async ESM module but was loaded as a CJS proxy.' + + '" is marked as an async ESM module but was loaded as a CJS proxy. ' + 'This is probably a bug in the React Server Components bundler.', ); } 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 f89b2fe46b2..b59eb05c7b3 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -677,7 +677,7 @@ describe('ReactFlightDOM', () => { root.render(); }); - const errorMessage = `The module "${Object.keys(webpackMap).at(0)}" is marked as an async ESM module but was loaded as a CJS proxy.This is probably a bug in the React Server Components bundler.`; + const errorMessage = `The module "${Object.keys(webpackMap).at(0)}" is marked as an async ESM module but was loaded as a CJS proxy. This is probably a bug in the React Server Components bundler.`; expect(container.innerHTML).toBe( __DEV__ diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js b/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js index 1d7f286d4f5..f9d9bf4ea91 100644 --- a/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js +++ b/packages/react-server-dom-webpack/src/server/ReactFlightServerConfigWebpackBundler.js @@ -75,7 +75,7 @@ export function resolveClientReferenceMetadata( throw new Error( 'The module "' + modulePath + - '" is marked as an async ESM module but was loaded as a CJS proxy.' + + '" is marked as an async ESM module but was loaded as a CJS proxy. ' + 'This is probably a bug in the React Server Components bundler.', ); } From cc98c203d56aab05664c9f8426470d944359dd44 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 21:31:58 +0200 Subject: [PATCH 09/10] Re-add `async` to `ImportManifestEntry` type --- .../src/shared/ReactFlightImportMetadata.js | 1 + .../src/shared/ReactFlightImportMetadata.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js b/packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js index 60460d9c1d6..7cfce93deb2 100644 --- a/packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js +++ b/packages/react-server-dom-turbopack/src/shared/ReactFlightImportMetadata.js @@ -12,6 +12,7 @@ export type ImportManifestEntry = { // chunks is an array of filenames chunks: Array, name: string, + async?: boolean, }; // This is the parsed shape of the wire format which is why it is diff --git a/packages/react-server-dom-webpack/src/shared/ReactFlightImportMetadata.js b/packages/react-server-dom-webpack/src/shared/ReactFlightImportMetadata.js index 08aafaf00c6..29b012f6052 100644 --- a/packages/react-server-dom-webpack/src/shared/ReactFlightImportMetadata.js +++ b/packages/react-server-dom-webpack/src/shared/ReactFlightImportMetadata.js @@ -12,6 +12,7 @@ export type ImportManifestEntry = { // chunks is a double indexed array of chunkId / chunkFilename pairs chunks: Array, name: string, + async?: boolean, }; // This is the parsed shape of the wire format which is why it is From c1b4272421af6ae808323c9ba591388d8113b1b2 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 13 Sep 2024 21:39:13 +0200 Subject: [PATCH 10/10] Fix wrong usage of `exports` --- .../src/__tests__/utils/TurbopackMock.js | 8 ++++++-- .../src/__tests__/utils/WebpackMock.js | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js b/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js index ee0f00c942e..2e81d55fa69 100644 --- a/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js +++ b/packages/react-server-dom-turbopack/src/__tests__/utils/TurbopackMock.js @@ -131,12 +131,16 @@ exports.clientExportsESM = function clientExportsESM( typeof moduleExports.then === 'function' ) { return moduleExports.then( - exports => createClientReferencesForExports({exports, async: true}), + asyncModuleExports => + createClientReferencesForExports({ + exports: asyncModuleExports, + async: true, + }), () => {}, ); } - return createClientReferencesForExports({exports}); + return createClientReferencesForExports({exports: moduleExports}); }; // This tests server to server references. There's another case of client to server references. diff --git a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js index d17eb42a933..654bcdc9b6d 100644 --- a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js +++ b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js @@ -177,12 +177,16 @@ exports.clientExportsESM = function clientExportsESM( typeof moduleExports.then === 'function' ) { return moduleExports.then( - exports => createClientReferencesForExports({exports, async: true}), + asyncModuleExports => + createClientReferencesForExports({ + exports: asyncModuleExports, + async: true, + }), () => {}, ); } - return createClientReferencesForExports({exports}); + return createClientReferencesForExports({exports: moduleExports}); }; // This tests server to server references. There's another case of client to server references.