@samitouri / QOS-React-2 / commits / 40ff2395e4

[Flight] Prevent non-Server imports of aliased Server entrypoints (#20422)

* [Flight] Prevent non-Server imports of aliased Server entrypoints * Fix Flow + await * Tighten the types

Dan Abramov committed Dec 10, 2020 at 03:55 UTC 40ff2395e44d5f821ddcd4f56d5f9a53ee99ceb1
3 files changed +25 -16
fixtures/flight-browser/index.html
+1 -1
@@ -19,7 +19,7 @@
19 <script src="../../build/node_modules/react/umd/react.development.js"></script>
20 <script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
21 <script src="../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js"></script>
22 - <script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.server.development.js"></script>
22 + <script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js"></script>
23 <script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack.development.js"></script>
24 <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
25 <script type="text/babel">
packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js
+14 -9
@@ -18,7 +18,7 @@ type ResolveFunction = (
18 string,
19 ResolveContext,
20 ResolveFunction,
21 -) => Promise<{url: string}>;
21 +) => {url: string} | Promise<{url: string}>;
22
23 type GetSourceContext = {
24 format: string,
@@ -70,19 +70,24 @@ export async function resolve(
70 );
71 }
72 }
73 - // We intentionally check the specifier here instead of the resolved file.
74 - // This allows package exports to configure non-server aliases that resolve to server files
75 - // depending on environment. It's probably a bad idea to export a server file as "main" though.
76 - if (specifier.endsWith('.server.js')) {
77 - if (context.parentURL && !context.parentURL.endsWith('.server.js')) {
73 + const resolved = await defaultResolve(specifier, context, defaultResolve);
74 + if (resolved.url.endsWith('.server.js')) {
75 + const parentURL = context.parentURL;
76 + if (parentURL && !parentURL.endsWith('.server.js')) {
77 + let reason;
78 + if (specifier.endsWith('.server.js')) {
79 + reason = `"${specifier}"`;
80 + } else {
81 + reason = `"${specifier}" (which expands to "${resolved.url}")`;
82 + }
83 throw new Error(
79 - `Cannot import "${specifier}" from "${context.parentURL}". ` +
84 + `Cannot import ${reason} from "${parentURL}". ` +
85 'By react-server convention, .server.js files can only be imported from other .server.js files. ' +
86 'That way nobody accidentally sends these to the client by indirectly importing it.',
87 );
88 }
89 }
85 - return defaultResolve(specifier, context, defaultResolve);
90 + return resolved;
91 }
92
93 export async function getSource(
@@ -128,7 +133,7 @@ function addExportNames(names, node) {
133 function resolveClientImport(
134 specifier: string,
135 parentURL: string,
131 -): Promise<{url: string}> {
136 +): {url: string} | Promise<{url: string}> {
137 // Resolve an import specifier as if it was loaded by the client. This doesn't use
138 // the overrides that this loader does but instead reverts to the default.
139 // This resolution algorithm will not necessarily have the same configuration
packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js
+10 -6
@@ -70,22 +70,26 @@ module.exports = function register() {
70 const originalResolveFilename = Module._resolveFilename;
71
72 Module._resolveFilename = function(request, parent, isMain, options) {
73 - // We intentionally check the request here instead of the resolved file.
74 - // This allows package exports to configure non-server aliases that resolve to server files
75 - // depending on environment. It's probably a bad idea to export a server file as "main" though.
76 - if (request.endsWith('.server.js')) {
73 + const resolved = originalResolveFilename.apply(this, arguments);
74 + if (resolved.endsWith('.server.js')) {
75 if (
76 parent &&
77 parent.filename &&
78 !parent.filename.endsWith('.server.js')
79 ) {
80 + let reason;
81 + if (request.endsWith('.server.js')) {
82 + reason = `"${request}"`;
83 + } else {
84 + reason = `"${request}" (which expands to "${resolved}")`;
85 + }
86 throw new Error(
83 - `Cannot import "${request}" from "${parent.filename}". ` +
87 + `Cannot import ${reason} from "${parent.filename}". ` +
88 'By react-server convention, .server.js files can only be imported from other .server.js files. ' +
89 'That way nobody accidentally sends these to the client by indirectly importing it.',
90 );
91 }
92 }
89 - return originalResolveFilename.apply(this, arguments);
93 + return resolved;
94 };
95 };