@samitouri / QOS-React-2 / commits / 565148d751

Disallow *.server.js imports from any other files (#20309)

This convention ensures that you can declare that you intend for a file to only be used on the server (even if it technically might resolve on the client).

Sebastian Markbåge committed Nov 30, 2020 at 17:25 UTC 565148d7516f9f0c4d90a7f06ab479c79ad56c69
5 files changed +56 -3
fixtures/flight/server/cli.server.js renamed
fixtures/flight/server/package.json
+2 -1
@@ -1,3 +1,4 @@
1 {
2 - "type": "commonjs"
2 + "type": "commonjs",
3 + "main": "./cli.server.js"
4 }
packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeLoader.js
+28 -1
@@ -31,12 +31,39 @@ type GetSourceFunction = (
31
32 type Source = string | ArrayBuffer | Uint8Array;
33
34 +let warnedAboutConditionsFlag = false;
35 +
36 export async function resolve(
37 specifier: string,
38 context: ResolveContext,
39 defaultResolve: ResolveFunction,
40 ): Promise<string> {
39 - // TODO: Resolve server-only files.
41 + if (!context.conditions.includes('react-server')) {
42 + context = {
43 + ...context,
44 + conditions: [...context.conditions, 'react-server'],
45 + };
46 + if (!warnedAboutConditionsFlag) {
47 + warnedAboutConditionsFlag = true;
48 + // eslint-disable-next-line react-internal/no-production-logging
49 + console.warn(
50 + 'You did not run Node.js with the `--conditions react-server` flag. ' +
51 + 'Any "react-server" override will only work with ESM imports.',
52 + );
53 + }
54 + }
55 + // We intentionally check the specifier here instead of the resolved file.
56 + // This allows package exports to configure non-server aliases that resolve to server files
57 + // depending on environment. It's probably a bad idea to export a server file as "main" though.
58 + if (specifier.endsWith('.server.js')) {
59 + if (context.parentURL && !context.parentURL.endsWith('.server.js')) {
60 + throw new Error(
61 + `Cannot import "${specifier}" from "${context.parentURL}". ` +
62 + 'By react-server convention, .server.js files can only be imported from other .server.js files. ' +
63 + 'That way nobody accidentally sends these to the client by indirectly importing it.',
64 + );
65 + }
66 + }
67 return defaultResolve(specifier, context, defaultResolve);
68 }
69
packages/react-transport-dom-webpack/src/ReactFlightWebpackNodeRegister.js
+25
@@ -9,6 +9,9 @@
9
10 const url = require('url');
11
12 +// $FlowFixMe
13 +const Module = require('module');
14 +
15 module.exports = function register() {
16 (require: any).extensions['.client.js'] = function(module, path) {
17 module.exports = {
@@ -16,4 +19,26 @@ module.exports = function register() {
19 name: url.pathToFileURL(path).href,
20 };
21 };
22 +
23 + const originalResolveFilename = Module._resolveFilename;
24 +
25 + Module._resolveFilename = function(request, parent, isMain, options) {
26 + // We intentionally check the request here instead of the resolved file.
27 + // This allows package exports to configure non-server aliases that resolve to server files
28 + // depending on environment. It's probably a bad idea to export a server file as "main" though.
29 + if (request.endsWith('.server.js')) {
30 + if (
31 + parent &&
32 + parent.filename &&
33 + !parent.filename.endsWith('.server.js')
34 + ) {
35 + throw new Error(
36 + `Cannot import "${request}" from "${parent.filename}". ` +
37 + 'By react-server convention, .server.js files can only be imported from other .server.js files. ' +
38 + 'That way nobody accidentally sends these to the client by indirectly importing it.',
39 + );
40 + }
41 + }
42 + return originalResolveFilename.apply(this, arguments);
43 + };
44 };
scripts/rollup/bundles.js
+1 -1
@@ -310,7 +310,7 @@ const bundles = [
310 moduleType: RENDERER_UTILS,
311 entry: 'react-transport-dom-webpack/node-register',
312 global: 'ReactFlightWebpackNodeRegister',
313 - externals: ['url'],
313 + externals: ['url', 'module'],
314 },
315
316 /******* React Transport DOM Server Relay *******/