diff --git a/fixtures/flight/server/index.js b/fixtures/flight/server/cli.server.js similarity index 100% rename from fixtures/flight/server/index.js rename to fixtures/flight/server/cli.server.js diff --git a/fixtures/flight/server/package.json b/fixtures/flight/server/package.json index 5bbefffbabee3..59055eee7d5bd 100644 --- a/fixtures/flight/server/package.json +++ b/fixtures/flight/server/package.json @@ -1,3 +1,4 @@ { - "type": "commonjs" + "type": "commonjs", + "main": "./cli.server.js" } diff --git a/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeLoader.js b/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeLoader.js index f9fd2a54e24f6..102da33028765 100644 --- a/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeLoader.js +++ b/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeLoader.js @@ -31,12 +31,39 @@ type GetSourceFunction = ( type Source = string | ArrayBuffer | Uint8Array; +let warnedAboutConditionsFlag = false; + export async function resolve( specifier: string, context: ResolveContext, defaultResolve: ResolveFunction, ): Promise { - // TODO: Resolve server-only files. + if (!context.conditions.includes('react-server')) { + context = { + ...context, + conditions: [...context.conditions, 'react-server'], + }; + if (!warnedAboutConditionsFlag) { + warnedAboutConditionsFlag = true; + // eslint-disable-next-line react-internal/no-production-logging + console.warn( + 'You did not run Node.js with the `--conditions react-server` flag. ' + + 'Any "react-server" override will only work with ESM imports.', + ); + } + } + // We intentionally check the specifier here instead of the resolved file. + // This allows package exports to configure non-server aliases that resolve to server files + // depending on environment. It's probably a bad idea to export a server file as "main" though. + if (specifier.endsWith('.server.js')) { + if (context.parentURL && !context.parentURL.endsWith('.server.js')) { + throw new Error( + `Cannot import "${specifier}" from "${context.parentURL}". ` + + 'By react-server convention, .server.js files can only be imported from other .server.js files. ' + + 'That way nobody accidentally sends these to the client by indirectly importing it.', + ); + } + } return defaultResolve(specifier, context, defaultResolve); } diff --git a/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeRegister.js b/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeRegister.js index f5149be1c42c4..17c3b8ef9d9c6 100644 --- a/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeRegister.js +++ b/packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeRegister.js @@ -9,6 +9,9 @@ const url = require('url'); +// $FlowFixMe +const Module = require('module'); + module.exports = function register() { (require: any).extensions['.client.js'] = function(module, path) { module.exports = { @@ -16,4 +19,26 @@ module.exports = function register() { name: url.pathToFileURL(path).href, }; }; + + const originalResolveFilename = Module._resolveFilename; + + Module._resolveFilename = function(request, parent, isMain, options) { + // We intentionally check the request here instead of the resolved file. + // This allows package exports to configure non-server aliases that resolve to server files + // depending on environment. It's probably a bad idea to export a server file as "main" though. + if (request.endsWith('.server.js')) { + if ( + parent && + parent.filename && + !parent.filename.endsWith('.server.js') + ) { + throw new Error( + `Cannot import "${request}" from "${parent.filename}". ` + + 'By react-server convention, .server.js files can only be imported from other .server.js files. ' + + 'That way nobody accidentally sends these to the client by indirectly importing it.', + ); + } + } + return originalResolveFilename.apply(this, arguments); + }; }; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index f39d197eb0363..641e6dd260271 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -310,7 +310,7 @@ const bundles = [ moduleType: RENDERER_UTILS, entry: 'react-transport-dom-webpack/node-register', global: 'ReactFlightWebpackNodeRegister', - externals: ['url'], + externals: ['url', 'module'], }, /******* React Transport DOM Server Relay *******/