From d7dd77b0d3e6c7b9c11508b2769f16afe6bff960 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 18 Aug 2022 10:45:32 -0400 Subject: [PATCH] Test the node-register hooks in unit tests --- .../react-server-dom-webpack/node-register.js | 2 +- .../react-server-dom-webpack/package.json | 1 + .../src/ReactFlightWebpackNodeRegister.js | 2 +- .../src/__tests__/ReactFlightDOM-test.js | 37 +++------- .../__tests__/ReactFlightDOMBrowser-test.js | 44 ++++-------- .../src/__tests__/utils/WebpackMock.js | 67 +++++++++++++++++++ scripts/rollup/bundles.js | 3 +- 7 files changed, 94 insertions(+), 62 deletions(-) create mode 100644 packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js diff --git a/packages/react-server-dom-webpack/node-register.js b/packages/react-server-dom-webpack/node-register.js index 03754438bf338..e0490f3a17686 100644 --- a/packages/react-server-dom-webpack/node-register.js +++ b/packages/react-server-dom-webpack/node-register.js @@ -7,4 +7,4 @@ * @flow */ -export * from './src/ReactFlightWebpackNodeRegister'; +module.exports = require('./src/ReactFlightWebpackNodeRegister'); diff --git a/packages/react-server-dom-webpack/package.json b/packages/react-server-dom-webpack/package.json index 0a8c3389de711..45ba3669f90af 100644 --- a/packages/react-server-dom-webpack/package.json +++ b/packages/react-server-dom-webpack/package.json @@ -35,6 +35,7 @@ "./writer.browser.server": "./writer.browser.server.js", "./node-loader": "./esm/react-server-dom-webpack-node-loader.js", "./node-register": "./node-register.js", + "./src/*": "./src/*", "./package.json": "./package.json" }, "main": "index.js", diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js index a5f889d3eb15d..1b528fba43021 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js @@ -57,7 +57,7 @@ module.exports = function register() { }, }; - (require: any).extensions['.client.js'] = function(module, path) { + Module._extensions['.client.js'] = function(module, path) { const moduleId = url.pathToFileURL(path).href; const moduleReference: {[string]: any} = { $$typeof: MODULE_REFERENCE, 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 b68a9b28284bc..4616246bb50bc 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -17,14 +17,9 @@ global.TextDecoder = require('util').TextDecoder; // TODO: we can replace this with FlightServer.act(). global.setImmediate = cb => cb(); -let webpackModuleIdx = 0; -let webpackModules = {}; -let webpackMap = {}; -global.__webpack_require__ = function(id) { - return webpackModules[id]; -}; - let act; +let clientExports; +let webpackMap; let Stream; let React; let ReactDOMClient; @@ -35,15 +30,17 @@ let Suspense; describe('ReactFlightDOM', () => { beforeEach(() => { jest.resetModules(); - webpackModules = {}; - webpackMap = {}; act = require('jest-react').act; + const WebpackMock = require('./utils/WebpackMock'); + clientExports = WebpackMock.clientExports; + webpackMap = WebpackMock.webpackMap; + Stream = require('stream'); React = require('react'); + Suspense = React.Suspense; ReactDOMClient = require('react-dom/client'); ReactServerDOMWriter = require('react-server-dom-webpack/writer.node.server'); ReactServerDOMReader = require('react-server-dom-webpack'); - Suspense = React.Suspense; }); function getTestStream() { @@ -64,22 +61,6 @@ describe('ReactFlightDOM', () => { }; } - function moduleReference(moduleExport) { - const idx = webpackModuleIdx++; - webpackModules[idx] = { - d: moduleExport, - }; - webpackMap['path/' + idx] = { - default: { - id: '' + idx, - chunks: [], - name: 'd', - }, - }; - const MODULE_TAG = Symbol.for('react.module.reference'); - return {$$typeof: MODULE_TAG, filepath: 'path/' + idx, name: 'default'}; - } - async function waitForSuspense(fn) { while (true) { try { @@ -345,7 +326,7 @@ describe('ReactFlightDOM', () => { return
{games}
; } - const MyErrorBoundaryClient = moduleReference(MyErrorBoundary); + const MyErrorBoundaryClient = clientExports(MyErrorBoundary); function ProfileContent() { return ( @@ -470,7 +451,7 @@ describe('ReactFlightDOM', () => { return ; } - const InputClient = moduleReference(Input); + const InputClient = clientExports(Input); // Server 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 e2cc4989c8eef..43573af1df141 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -14,13 +14,9 @@ global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStr global.TextEncoder = require('util').TextEncoder; global.TextDecoder = require('util').TextDecoder; -let webpackModuleIdx = 0; -let webpackModules = {}; -let webpackMap = {}; -global.__webpack_require__ = function(id) { - return webpackModules[id]; -}; - +let clientExports; +let webpackMap; +let webpackModules; let act; let React; let ReactDOMClient; @@ -32,9 +28,11 @@ let Suspense; describe('ReactFlightDOMBrowser', () => { beforeEach(() => { jest.resetModules(); - webpackModules = {}; - webpackMap = {}; act = require('jest-react').act; + const WebpackMock = require('./utils/WebpackMock'); + clientExports = WebpackMock.clientExports; + webpackMap = WebpackMock.webpackMap; + webpackModules = WebpackMock.webpackModules; React = require('react'); ReactDOMClient = require('react-dom/client'); ReactDOMServer = require('react-dom/server.browser'); @@ -43,22 +41,6 @@ describe('ReactFlightDOMBrowser', () => { Suspense = React.Suspense; }); - function moduleReference(moduleExport) { - const idx = webpackModuleIdx++; - webpackModules[idx] = { - d: moduleExport, - }; - webpackMap['path/' + idx] = { - default: { - id: '' + idx, - chunks: [], - name: 'd', - }, - }; - const MODULE_TAG = Symbol.for('react.module.reference'); - return {$$typeof: MODULE_TAG, filepath: 'path/' + idx, name: 'default'}; - } - async function waitForSuspense(fn) { while (true) { try { @@ -249,7 +231,7 @@ describe('ReactFlightDOMBrowser', () => { return
{games}
; } - const MyErrorBoundaryClient = moduleReference(MyErrorBoundary); + const MyErrorBoundaryClient = clientExports(MyErrorBoundary); function ProfileContent() { return ( @@ -478,19 +460,19 @@ describe('ReactFlightDOMBrowser', () => { } // The Client build may not have the same IDs as the Server bundles for the same // component. - const ClientComponentOnTheClient = moduleReference(ClientComponent); - const ClientComponentOnTheServer = moduleReference(ClientComponent); + const ClientComponentOnTheClient = clientExports(ClientComponent); + const ClientComponentOnTheServer = clientExports(ClientComponent); // In the SSR bundle this module won't exist. We simulate this by deleting it. - const clientId = webpackMap[ClientComponentOnTheClient.filepath].default.id; + const clientId = webpackMap[ClientComponentOnTheClient.filepath]['*'].id; delete webpackModules[clientId]; // Instead, we have to provide a translation from the client meta data to the SSR // meta data. - const ssrMetaData = webpackMap[ClientComponentOnTheServer.filepath].default; + const ssrMetaData = webpackMap[ClientComponentOnTheServer.filepath]['*']; const translationMap = { [clientId]: { - d: ssrMetaData, + '*': ssrMetaData, }, }; diff --git a/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js new file mode 100644 index 0000000000000..2cd21b0f2df14 --- /dev/null +++ b/packages/react-server-dom-webpack/src/__tests__/utils/WebpackMock.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const url = require('url'); +const Module = require('module'); + +let webpackModuleIdx = 0; +const webpackModules = {}; +const webpackMap = {}; +global.__webpack_require__ = function(id) { + return webpackModules[id]; +}; + +const previousLoader = Module._extensions['.client.js']; + +const register = require('react-server-dom-webpack/node-register'); +// Register node loader +register(); + +const nodeLoader = Module._extensions['.client.js']; + +if (previousLoader === nodeLoader) { + throw new Error( + 'Expected the Node loader to register the .client.js extension', + ); +} + +Module._extensions['.client.js'] = previousLoader; + +exports.webpackMap = webpackMap; +exports.webpackModules = webpackModules; + +exports.clientExports = function clientExports(moduleExports) { + const idx = '' + webpackModuleIdx++; + webpackModules[idx] = moduleExports; + const path = url.pathToFileURL(idx).href; + webpackMap[path] = { + '': { + id: idx, + chunks: [], + name: '', + }, + '*': { + id: idx, + chunks: [], + name: '*', + }, + }; + for (const name in moduleExports) { + webpackMap[path] = { + [name]: { + id: idx, + chunks: [], + name: name, + }, + }; + } + const mod = {exports: {}}; + nodeLoader(mod, idx); + return mod.exports; +}; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 68407ef01183d..f93225d9d15da 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -408,7 +408,8 @@ const bundles = [ { bundleTypes: [NODE_ES2015], moduleType: RENDERER_UTILS, - entry: 'react-server-dom-webpack/node-register', + entry: 'react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js', + name: 'react-server-dom-webpack-node-register', global: 'ReactFlightWebpackNodeRegister', minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: false,