[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
This commit is contained in:
Dan Abramov 2020-12-10 03:55:38 +00:00 committed by GitHub
parent 94aa365e3a
commit 40ff2395e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 25 additions and 16 deletions

View File

@ -19,7 +19,7 @@
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js"></script>
<script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.server.development.js"></script>
<script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack-writer.browser.development.server.js"></script>
<script src="../../build/node_modules/react-server-dom-webpack/umd/react-server-dom-webpack.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">

View File

@ -18,7 +18,7 @@ type ResolveFunction = (
string,
ResolveContext,
ResolveFunction,
) => Promise<{url: string}>;
) => {url: string} | Promise<{url: string}>;
type GetSourceContext = {
format: string,
@ -70,19 +70,24 @@ export async function resolve(
);
}
}
// 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')) {
const resolved = await defaultResolve(specifier, context, defaultResolve);
if (resolved.url.endsWith('.server.js')) {
const parentURL = context.parentURL;
if (parentURL && !parentURL.endsWith('.server.js')) {
let reason;
if (specifier.endsWith('.server.js')) {
reason = `"${specifier}"`;
} else {
reason = `"${specifier}" (which expands to "${resolved.url}")`;
}
throw new Error(
`Cannot import "${specifier}" from "${context.parentURL}". ` +
`Cannot import ${reason} from "${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);
return resolved;
}
export async function getSource(
@ -128,7 +133,7 @@ function addExportNames(names, node) {
function resolveClientImport(
specifier: string,
parentURL: string,
): Promise<{url: string}> {
): {url: string} | Promise<{url: string}> {
// Resolve an import specifier as if it was loaded by the client. This doesn't use
// the overrides that this loader does but instead reverts to the default.
// This resolution algorithm will not necessarily have the same configuration

View File

@ -70,22 +70,26 @@ module.exports = function register() {
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')) {
const resolved = originalResolveFilename.apply(this, arguments);
if (resolved.endsWith('.server.js')) {
if (
parent &&
parent.filename &&
!parent.filename.endsWith('.server.js')
) {
let reason;
if (request.endsWith('.server.js')) {
reason = `"${request}"`;
} else {
reason = `"${request}" (which expands to "${resolved}")`;
}
throw new Error(
`Cannot import "${request}" from "${parent.filename}". ` +
`Cannot import ${reason} 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);
return resolved;
};
};