165 lines
5.1 KiB
JavaScript

import {
isModule,
rewriteModuleStatementsAndPrepareHeader,
isSideEffectImport,
buildNamespaceInitStatements,
ensureStatementsHoisted,
wrapInterop,
} from "@babel/helper-module-transforms";
import simplifyAccess from "@babel/helper-simple-access";
export default function({ types: t, template }, options) {
const {
loose,
allowTopLevelThis,
strict,
strictMode,
noInterop,
// Defaulting to 'true' for now. May change before 7.x major.
allowCommonJSExports = true,
} = options;
const moduleAssertion = template(`
(function(){
throw new Error("The CommonJS 'module' variable is not available in ES6 modules.");
})();
`);
const exportsAssertion = template(`
(function(){
throw new Error("The CommonJS 'exports' variable is not available in ES6 modules.");
})();
`);
const getAssertion = localName =>
(localName === "module" ? moduleAssertion() : exportsAssertion())
.expression;
const moduleExportsVisitor = {
ReferencedIdentifier(path) {
const localName = path.node.name;
if (localName !== "module" && localName !== "exports") return;
const localBinding = path.scope.getBinding(localName);
const rootBinding = this.scope.getBinding(localName);
if (
// redeclared in this scope
rootBinding !== localBinding ||
(path.parentPath.isObjectProperty({ value: path.node }) &&
path.parentPath.parentPath.isObjectPattern()) ||
path.parentPath.isAssignmentExpression({ left: path.node }) ||
path.isAssignmentExpression({ left: path.node })
) {
return;
}
path.replaceWith(getAssertion(localName));
},
AssignmentExpression(path) {
const left = path.get("left");
if (left.isIdentifier()) {
const localName = path.node.name;
if (localName !== "module" && localName !== "exports") return;
const localBinding = path.scope.getBinding(localName);
const rootBinding = this.scope.getBinding(localName);
// redeclared in this scope
if (rootBinding !== localBinding) return;
const right = path.get("right");
right.replaceWith(
t.sequenceExpression([right.node, getAssertion(localName)]),
);
} else if (left.isPattern()) {
const ids = left.getOuterBindingIdentifiers();
const localName = Object.keys(ids).filter(localName => {
if (localName !== "module" && localName !== "exports") return false;
return (
this.scope.getBinding(localName) ===
path.scope.getBinding(localName)
);
})[0];
if (localName) {
const right = path.get("right");
right.replaceWith(
t.sequenceExpression([right.node, getAssertion(localName)]),
);
}
}
},
};
return {
visitor: {
Program: {
exit(path) {
// For now this requires unambiguous rather that just sourceType
// because Babel currently parses all files as sourceType:module.
if (!isModule(path, true /* requireUnambiguous */)) return;
// Rename the bindings auto-injected into the scope so there is no
// risk of conflict between the bindings.
path.scope.rename("exports");
path.scope.rename("module");
path.scope.rename("require");
path.scope.rename("__filename");
path.scope.rename("__dirname");
// Rewrite references to 'module' and 'exports' to throw exceptions.
// These objects are specific to CommonJS and are not available in
// real ES6 implementations.
if (!allowCommonJSExports) {
simplifyAccess(path, new Set(["module", "exports"]));
path.traverse(moduleExportsVisitor, {
scope: path.scope,
});
}
let moduleName = this.getModuleName();
if (moduleName) moduleName = t.stringLiteral(moduleName);
const {
meta,
headers,
} = rewriteModuleStatementsAndPrepareHeader(path, {
exportName: "exports",
loose,
strict,
strictMode,
allowTopLevelThis,
noInterop,
});
for (const [source, metadata] of meta.source) {
const loadExpr = t.callExpression(t.identifier("require"), [
t.stringLiteral(source),
]);
let header;
if (isSideEffectImport(metadata)) {
header = t.expressionStatement(loadExpr);
} else {
header = t.variableDeclaration("var", [
t.variableDeclarator(
t.identifier(metadata.name),
wrapInterop(path, loadExpr, metadata.interop) || loadExpr,
),
]);
}
header.loc = metadata.loc;
headers.push(header);
headers.push(...buildNamespaceInitStatements(meta, metadata));
}
ensureStatementsHoisted(headers);
path.unshiftContainer("body", headers);
},
},
},
};
}