Implement importInterop: "node" option for module transforms (#12838)
This commit is contained in:
@@ -6,9 +6,10 @@ import { isModule } from "@babel/helper-module-imports";
|
||||
|
||||
import rewriteThis from "./rewrite-this";
|
||||
import rewriteLiveReferences from "./rewrite-live-references";
|
||||
import normalizeAndLoadModuleMetadata, {
|
||||
import normalizeModuleAndLoadMetadata, {
|
||||
hasExports,
|
||||
isSideEffectImport,
|
||||
validateImportInteropOption,
|
||||
} from "./normalize-and-load-metadata";
|
||||
import type {
|
||||
InteropType,
|
||||
@@ -38,6 +39,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
|
||||
allowTopLevelThis,
|
||||
strictMode,
|
||||
noInterop,
|
||||
importInterop = noInterop ? "none" : "babel",
|
||||
lazy,
|
||||
esNamespaceOnly,
|
||||
|
||||
@@ -49,6 +51,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
|
||||
allowTopLevelThis?;
|
||||
strictMode;
|
||||
loose?;
|
||||
importInterop?: "none" | "babel" | "node";
|
||||
noInterop?;
|
||||
lazy?;
|
||||
esNamespaceOnly?;
|
||||
@@ -56,11 +59,12 @@ export function rewriteModuleStatementsAndPrepareHeader(
|
||||
enumerableModuleMeta?;
|
||||
},
|
||||
) {
|
||||
validateImportInteropOption(importInterop);
|
||||
assert(isModule(path), "Cannot process module statements in a script");
|
||||
path.node.sourceType = "script";
|
||||
|
||||
const meta = normalizeAndLoadModuleMetadata(path, exportName, {
|
||||
noInterop,
|
||||
const meta = normalizeModuleAndLoadMetadata(path, exportName, {
|
||||
importInterop,
|
||||
initializeReexports: constantReexports,
|
||||
lazy,
|
||||
esNamespaceOnly,
|
||||
@@ -128,6 +132,15 @@ export function wrapInterop(
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === "node-namespace") {
|
||||
return t.callExpression(
|
||||
programPath.hub.addHelper("interopRequireWildcard"),
|
||||
[expr, t.booleanLiteral(true)],
|
||||
);
|
||||
} else if (type === "node-default") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let helper;
|
||||
if (type === "default") {
|
||||
helper = "interopRequireDefault";
|
||||
@@ -227,16 +240,18 @@ const buildReexportsFromMeta = (
|
||||
|
||||
const { stringSpecifiers } = meta;
|
||||
return Array.from(metadata.reexports, ([exportName, importName]) => {
|
||||
let NAMESPACE_IMPORT;
|
||||
if (stringSpecifiers.has(importName)) {
|
||||
let NAMESPACE_IMPORT: t.Expression = t.cloneNode(namespace);
|
||||
if (importName === "default" && metadata.interop === "node-default") {
|
||||
// Nothing, it's ok as-is
|
||||
} else if (stringSpecifiers.has(importName)) {
|
||||
NAMESPACE_IMPORT = t.memberExpression(
|
||||
t.cloneNode(namespace),
|
||||
NAMESPACE_IMPORT,
|
||||
t.stringLiteral(importName),
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
NAMESPACE_IMPORT = NAMESPACE_IMPORT = t.memberExpression(
|
||||
t.cloneNode(namespace),
|
||||
NAMESPACE_IMPORT = t.memberExpression(
|
||||
NAMESPACE_IMPORT,
|
||||
t.identifier(importName),
|
||||
);
|
||||
}
|
||||
@@ -245,7 +260,7 @@ const buildReexportsFromMeta = (
|
||||
EXPORT_NAME: exportName,
|
||||
NAMESPACE_IMPORT,
|
||||
};
|
||||
if (constantReexports) {
|
||||
if (constantReexports || t.isIdentifier(NAMESPACE_IMPORT)) {
|
||||
if (stringSpecifiers.has(exportName)) {
|
||||
return ReexportTemplate.constantComputed(astNodes);
|
||||
} else {
|
||||
|
||||
@@ -21,7 +21,12 @@ export interface ModuleMetadata {
|
||||
stringSpecifiers: Set<string>;
|
||||
}
|
||||
|
||||
export type InteropType = "default" | "namespace" | "none";
|
||||
export type InteropType =
|
||||
| "default" // Babel interop for default-only imports
|
||||
| "namespace" // Babel interop for namespace or default+named imports
|
||||
| "node-default" // Node.js interop for default-only imports
|
||||
| "node-namespace" // Node.js interop for namespace or default+named imports
|
||||
| "none"; // No interop, or named-only imports
|
||||
|
||||
export interface SourceModuleMetadata {
|
||||
// A unique variable name to use for this namespace object. Centralized for simplicity.
|
||||
@@ -68,19 +73,42 @@ export function isSideEffectImport(source: SourceModuleMetadata) {
|
||||
);
|
||||
}
|
||||
|
||||
export function validateImportInteropOption(
|
||||
importInterop: any,
|
||||
): importInterop is "none" | "babel" | "node" | Function {
|
||||
if (
|
||||
typeof importInterop !== "function" &&
|
||||
importInterop !== "none" &&
|
||||
importInterop !== "babel" &&
|
||||
importInterop !== "node"
|
||||
) {
|
||||
throw new Error(
|
||||
`.importInterop must be one of "none", "babel", "node", or a function returning one of those values (received ${importInterop}).`,
|
||||
);
|
||||
}
|
||||
return importInterop;
|
||||
}
|
||||
|
||||
function resolveImportInterop(importInterop, source) {
|
||||
if (typeof importInterop === "function") {
|
||||
return validateImportInteropOption(importInterop(source));
|
||||
}
|
||||
return importInterop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all imports and exports from the file, and return all metadata
|
||||
* needed to reconstruct the module's behavior.
|
||||
*/
|
||||
export default function normalizeModuleAndLoadMetadata(
|
||||
programPath: NodePath<t.Program>,
|
||||
exportName?: string,
|
||||
exportName: string,
|
||||
{
|
||||
noInterop = false,
|
||||
importInterop,
|
||||
initializeReexports = false,
|
||||
lazy = false,
|
||||
esNamespaceOnly = false,
|
||||
} = {},
|
||||
},
|
||||
): ModuleMetadata {
|
||||
if (!exportName) {
|
||||
exportName = programPath.scope.generateUidIdentifier("exports").name;
|
||||
@@ -105,16 +133,24 @@ export default function normalizeModuleAndLoadMetadata(
|
||||
metadata.name = metadata.importsNamespace.values().next().value;
|
||||
}
|
||||
|
||||
if (noInterop) metadata.interop = "none";
|
||||
else if (esNamespaceOnly) {
|
||||
const resolvedInterop = resolveImportInterop(
|
||||
importInterop,
|
||||
metadata.source,
|
||||
);
|
||||
|
||||
if (resolvedInterop === "none") {
|
||||
metadata.interop = "none";
|
||||
} else if (resolvedInterop === "node" && metadata.interop === "namespace") {
|
||||
metadata.interop = "node-namespace";
|
||||
} else if (resolvedInterop === "node" && metadata.interop === "default") {
|
||||
metadata.interop = "node-default";
|
||||
} else if (esNamespaceOnly && metadata.interop === "namespace") {
|
||||
// Both the default and namespace interops pass through __esModule
|
||||
// objects, but the namespace interop is used to enable Babel's
|
||||
// destructuring-like interop behavior for normal CommonJS.
|
||||
// Since some tooling has started to remove that behavior, we expose
|
||||
// it as the `esNamespace` option.
|
||||
if (metadata.interop === "namespace") {
|
||||
metadata.interop = "default";
|
||||
}
|
||||
metadata.interop = "default";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +235,8 @@ function getModuleMetadata(
|
||||
reexportAll: null,
|
||||
|
||||
lazy: false,
|
||||
|
||||
source,
|
||||
};
|
||||
sourceData.set(source, data);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ export default function rewriteLiveReferences(
|
||||
let namespace: t.Expression = t.identifier(meta.name);
|
||||
if (meta.lazy) namespace = t.callExpression(namespace, []);
|
||||
|
||||
if (importName === "default" && meta.interop === "node-default") {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
const computed = metadata.stringSpecifiers.has(importName);
|
||||
|
||||
return t.memberExpression(
|
||||
|
||||
Reference in New Issue
Block a user