[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
};