2017-06-27 12:15:00 -05:00

143 lines
3.5 KiB
JavaScript

import { basename, extname } from "path";
import template from "babel-template";
import transformCommonjs from "babel-plugin-transform-es2015-modules-commonjs";
const buildDefine = template(`
define(MODULE_NAME, [SOURCES], FACTORY);
`);
const buildFactory = template(`
(function (PARAMS) {
BODY;
})
`);
export default function({ types: t }) {
function isValidRequireCall(path) {
if (!path.isCallExpression()) return false;
if (!path.get("callee").isIdentifier({ name: "require" })) return false;
if (path.scope.getBinding("require")) return false;
const args = path.get("arguments");
if (args.length !== 1) return false;
const arg = args[0];
if (!arg.isStringLiteral()) return false;
return true;
}
function buildParamsAndSource(sourcesFound) {
const params = [];
const sources = [];
let hasSeenNonBareRequire = false;
for (let i = sourcesFound.length - 1; i > -1; i--) {
const source = sourcesFound[i];
sources.unshift(source[1]);
// bare import at end, no need for param
if (!hasSeenNonBareRequire && source[2] === true) {
continue;
}
hasSeenNonBareRequire = true;
params.unshift(source[0]);
}
return [params, sources];
}
const amdVisitor = {
ReferencedIdentifier({ node, scope }) {
if (node.name === "exports" && !scope.getBinding("exports")) {
this.hasExports = true;
}
if (node.name === "module" && !scope.getBinding("module")) {
this.hasModule = true;
}
},
CallExpression(path) {
if (!isValidRequireCall(path)) return;
const source = path.node.arguments[0];
const ref = path.scope.generateUidIdentifier(
basename(source.value, extname(source.value)),
);
this.sources.push([ref, source, true]);
path.remove();
},
VariableDeclarator(path) {
const id = path.get("id");
if (!id.isIdentifier()) return;
const init = path.get("init");
if (!isValidRequireCall(init)) return;
const source = init.node.arguments[0];
this.sourceNames[source.value] = true;
this.sources.push([id.node, source]);
path.remove();
},
};
return {
inherits: transformCommonjs,
pre() {
// source strings
this.sources = [];
this.sourceNames = Object.create(null);
this.hasExports = false;
this.hasModule = false;
},
visitor: {
Program: {
exit(path) {
if (this.ran) return;
this.ran = true;
path.traverse(amdVisitor, this);
const [params, sources] = buildParamsAndSource(this.sources);
let moduleName = this.getModuleName();
if (moduleName) moduleName = t.stringLiteral(moduleName);
if (this.hasExports) {
sources.unshift(t.stringLiteral("exports"));
params.unshift(t.identifier("exports"));
}
if (this.hasModule) {
sources.unshift(t.stringLiteral("module"));
params.unshift(t.identifier("module"));
}
const { node } = path;
const factory = buildFactory({
PARAMS: params,
BODY: node.body,
});
factory.expression.body.directives = node.directives;
node.directives = [];
node.body = [
buildDefine({
MODULE_NAME: moduleName,
SOURCES: sources,
FACTORY: factory,
}),
];
},
},
},
};
}