Skip to content

Commit 40ff239

Browse files
authored
[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
1 parent 94aa365 commit 40ff239

File tree

3 files changed

+25
-16
lines changed

3 files changed

+25
-16
lines changed

Diff for: fixtures/flight-browser/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ <h1>Flight Example</h1>
1919
<script src="../../build/node_modules/react/umd/react.development.js"></script>
2020
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
2121
<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>
2323
<script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack.development.js"></script>
2424
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
2525
<script type="text/babel">

Diff for: packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type ResolveFunction = (
1818
string,
1919
ResolveContext,
2020
ResolveFunction,
21-
) => Promise<{url: string}>;
21+
) => {url: string} | Promise<{url: string}>;
2222

2323
type GetSourceContext = {
2424
format: string,
@@ -70,19 +70,24 @@ export async function resolve(
7070
);
7171
}
7272
}
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+
}
7883
throw new Error(
79-
`Cannot import "${specifier}" from "${context.parentURL}". ` +
84+
`Cannot import ${reason} from "${parentURL}". ` +
8085
'By react-server convention, .server.js files can only be imported from other .server.js files. ' +
8186
'That way nobody accidentally sends these to the client by indirectly importing it.',
8287
);
8388
}
8489
}
85-
return defaultResolve(specifier, context, defaultResolve);
90+
return resolved;
8691
}
8792

8893
export async function getSource(
@@ -128,7 +133,7 @@ function addExportNames(names, node) {
128133
function resolveClientImport(
129134
specifier: string,
130135
parentURL: string,
131-
): Promise<{url: string}> {
136+
): {url: string} | Promise<{url: string}> {
132137
// Resolve an import specifier as if it was loaded by the client. This doesn't use
133138
// the overrides that this loader does but instead reverts to the default.
134139
// This resolution algorithm will not necessarily have the same configuration

Diff for: packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,26 @@ module.exports = function register() {
7070
const originalResolveFilename = Module._resolveFilename;
7171

7272
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')) {
7775
if (
7876
parent &&
7977
parent.filename &&
8078
!parent.filename.endsWith('.server.js')
8179
) {
80+
let reason;
81+
if (request.endsWith('.server.js')) {
82+
reason = `"${request}"`;
83+
} else {
84+
reason = `"${request}" (which expands to "${resolved}")`;
85+
}
8286
throw new Error(
83-
`Cannot import "${request}" from "${parent.filename}". ` +
87+
`Cannot import ${reason} from "${parent.filename}". ` +
8488
'By react-server convention, .server.js files can only be imported from other .server.js files. ' +
8589
'That way nobody accidentally sends these to the client by indirectly importing it.',
8690
);
8791
}
8892
}
89-
return originalResolveFilename.apply(this, arguments);
93+
return resolved;
9094
};
9195
};

0 commit comments

Comments
 (0)