Implement importInterop: "node" option for module transforms (#12838)

This commit is contained in:
Nicolò Ribaudo
2021-04-28 18:22:47 +02:00
committed by GitHub
parent 22b0eb038f
commit be03be1bc3
101 changed files with 834 additions and 32 deletions

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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(