Skip to content

Commit

Permalink
Implement importInterop: "node" option for module transforms (#12838)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Apr 28, 2021
1 parent 22b0eb0 commit be03be1
Show file tree
Hide file tree
Showing 101 changed files with 834 additions and 32 deletions.
33 changes: 24 additions & 9 deletions packages/babel-helper-module-transforms/src/index.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -38,6 +39,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
allowTopLevelThis,
strictMode,
noInterop,
importInterop = noInterop ? "none" : "babel",
lazy,
esNamespaceOnly,

Expand All @@ -49,18 +51,20 @@ export function rewriteModuleStatementsAndPrepareHeader(
allowTopLevelThis?;
strictMode;
loose?;
importInterop?: "none" | "babel" | "node";
noInterop?;
lazy?;
esNamespaceOnly?;
constantReexports?;
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,
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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),
);
}
Expand All @@ -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 {
Expand Down
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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";
}
}

Expand Down Expand Up @@ -199,6 +235,8 @@ function getModuleMetadata(
reexportAll: null,

lazy: false,

source,
};
sourceData.set(source, data);
}
Expand Down
Expand Up @@ -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(
Expand Down
20 changes: 11 additions & 9 deletions packages/babel-helpers/src/helpers.js
Expand Up @@ -500,33 +500,35 @@ helpers.interopRequireDefault = helper("7.0.0-beta.0")`
}
`;

helpers.interopRequireWildcard = helper("7.0.0-beta.0")`
function _getRequireWildcardCache() {
helpers.interopRequireWildcard = helper("7.14.0")`
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cache = new WeakMap();
_getRequireWildcardCache = function () { return cache; };
return cache;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function (nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
export default function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
export default function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || (typeof obj !== "object" && typeof obj !== "function")) {
return { default: obj }
}
var cache = _getRequireWildcardCache();
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
Expand Down
3 changes: 2 additions & 1 deletion packages/babel-plugin-transform-modules-amd/package.json
Expand Up @@ -26,6 +26,7 @@
},
"devDependencies": {
"@babel/core": "workspace:*",
"@babel/helper-plugin-test-runner": "workspace:*"
"@babel/helper-plugin-test-runner": "workspace:*",
"@babel/plugin-external-helpers": "workspace:*"
}
}
9 changes: 8 additions & 1 deletion packages/babel-plugin-transform-modules-amd/src/index.js
Expand Up @@ -38,7 +38,13 @@ function injectWrapper(path, wrapper) {
export default declare((api, options) => {
api.assertVersion(7);

const { allowTopLevelThis, strict, strictMode, noInterop } = options;
const {
allowTopLevelThis,
strict,
strictMode,
importInterop,
noInterop,
} = options;

const constantReexports =
api.assumption("constantReexports") ?? options.loose;
Expand Down Expand Up @@ -114,6 +120,7 @@ export default declare((api, options) => {
strict,
strictMode,
allowTopLevelThis,
importInterop,
noInterop,
},
);
Expand Down
@@ -0,0 +1 @@
export { default } from 'dep';
@@ -0,0 +1,8 @@
define(["exports", "dep"], function (_exports, _dep) {
"use strict";

Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = _dep;
});
@@ -0,0 +1 @@
export { default, name } from 'dep';
@@ -0,0 +1,20 @@
define(["exports", "dep"], function (_exports, _dep) {
"use strict";

Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _dep.default;
}
});
Object.defineProperty(_exports, "name", {
enumerable: true,
get: function () {
return _dep.name;
}
});
_dep = babelHelpers.interopRequireWildcard(_dep, true);
});
@@ -0,0 +1 @@
export { name } from 'dep';
@@ -0,0 +1,13 @@
define(["exports", "dep"], function (_exports, _dep) {
"use strict";

Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "name", {
enumerable: true,
get: function () {
return _dep.name;
}
});
});
@@ -0,0 +1,3 @@
import foo from "foo";

foo();
@@ -0,0 +1,5 @@
define(["foo"], function (_foo) {
"use strict";

_foo();
});
@@ -0,0 +1,4 @@
import foo, { named } from "foo";

foo();
named();
@@ -0,0 +1,7 @@
define(["foo"], function (_foo) {
"use strict";

_foo = babelHelpers.interopRequireWildcard(_foo, true);
(0, _foo.default)();
(0, _foo.named)();
});
@@ -0,0 +1,3 @@
import { named } from "foo";

named();
@@ -0,0 +1,5 @@
define(["foo"], function (_foo) {
"use strict";

(0, _foo.named)();
});
@@ -0,0 +1,4 @@
import * as foo from 'foo';

foo.bar();
foo.baz();
@@ -0,0 +1,7 @@
define(["foo"], function (foo) {
"use strict";

foo = babelHelpers.interopRequireWildcard(foo, true);
foo.bar();
foo.baz();
});
@@ -0,0 +1,3 @@
{
"plugins": [["transform-modules-amd", { "importInterop": "node" }]]
}
@@ -0,0 +1 @@
export { default } from 'foo';
@@ -0,0 +1,13 @@
define(["exports", "foo"], function (_exports, _foo) {
"use strict";

Object.defineProperty(_exports, "__esModule", {
value: true
});
Object.defineProperty(_exports, "default", {
enumerable: true,
get: function () {
return _foo.default;
}
});
});
@@ -0,0 +1,3 @@
import foo from "foo";

foo();

0 comments on commit be03be1

Please sign in to comment.