@zacharygriffee/rollup-plugin-external-globals
Version:
Transform external imports into global variables like output.globals.
152 lines (139 loc) • 5.14 kB
JavaScript
import {attachScopes, makeLegalIdentifier} from "./pluginutils.js";
import {walk} from "estree-walker";
import isReference from "is-reference";
function analyzeImport(node, importBindings, code, getName, globals) {
const name = node.source.value && getName(node.source.value);
if (!name) {
return false;
}
globals.add(name);
for (const spec of node.specifiers) {
importBindings.set(spec.local.name, makeGlobalName(spec.imported ? spec.imported.name : "default", name));
}
code.remove(node.start, node.end);
return true;
}
function makeGlobalName(prop, name) {
if (prop === "default") {
return name;
}
return `${name}.${prop}`;
}
function writeSpecLocal(code, root, spec, name, tempNames) {
if (spec.isOverwritten) return;
// we always need an extra assignment for named export statement
// https://github.com/eight04/rollup-plugin-external-globals/issues/19
const localName = `_global_${makeLegalIdentifier(name)}`;
if (!tempNames.has(localName)) {
code.appendRight(root.start, `const ${localName} = ${name};\n`);
tempNames.add(localName);
}
if (spec.local.name === localName) {
return;
}
if (spec.local === spec.exported) {
code.appendRight(spec.local.start, `${localName} as `);
} else {
code.overwrite(spec.local.start, spec.local.end, localName);
}
spec.isOverwritten = true;
}
function writeIdentifier(code, node, parent, name) {
if (node.name === name || node.isOverwritten) {
return;
}
// 2020/8/14, parent.key and parent.value is no longer the same object. However, the shape is the same.
if (parent.type === "Property" && parent.key.start === parent.value.start) {
code.appendLeft(node.end, `: ${name}`);
parent.key.isOverwritten = true;
parent.value.isOverwritten = true;
} else {
code.overwrite(node.start, node.end, name, {contentOnly: true});
// FIXME: do we need this?
node.isOverwritten = true;
}
}
function analyzeExportNamed(node, code, getName, tempNames) {
if (node.declaration || !node.source || !node.source.value) {
return false;
}
const name = getName(node.source.value);
if (!name) {
return false;
}
for (const spec of node.specifiers) {
const globalName = makeGlobalName(spec.local.name, name);
writeSpecLocal(code, node, spec, globalName, tempNames);
}
if (node.specifiers.length) {
code.overwrite(node.specifiers[node.specifiers.length - 1].end, node.source.end, "}");
} else {
code.remove(node.start, node.end);
}
return true;
}
function writeDynamicImport(code, node, content) {
code.overwrite(node.start, node.end, content);
}
function getDynamicImportSource(node) {
if (node.type === "ImportExpression") {
return node.source.value;
}
if (node.type === "CallExpression" && node.callee.type === "Import") {
return node.arguments[0].value;
}
}
async function importToGlobals({ast, code, getName, getDynamicWrapper}) {
let scope = attachScopes(ast, "scope");
const bindings = new Map;
const globals = new Set;
let isTouched = false;
const tempNames = new Set;
for (const node of ast.body) {
if (node.type === "ImportDeclaration") {
isTouched = analyzeImport(node, bindings, code, getName, globals) || isTouched;
} else if (node.type === "ExportNamedDeclaration") {
isTouched = analyzeExportNamed(node, code, getName, tempNames) || isTouched;
}
}
let topStatement;
walk(ast, {
enter(node, parent) {
if (parent && parent.type === "Program") {
topStatement = node;
}
if (/^importdec/i.test(node.type)) {
this.skip();
return;
}
if (node.scope) {
scope = node.scope;
}
if (isReference(node, parent)) {
if (bindings.has(node.name) && !scope.contains(node.name)) {
if (parent.type === "ExportSpecifier") {
writeSpecLocal(code, topStatement, parent, bindings.get(node.name), tempNames);
} else {
writeIdentifier(code, node, parent, bindings.get(node.name));
}
} else if (globals.has(node.name) && scope.contains(node.name)) {
writeIdentifier(code, node, parent, `_local_${node.name}`);
}
}
const source = getDynamicImportSource(node);
const name = source && getName(source);
const dynamicName = name && getDynamicWrapper(name);
if (dynamicName) {
writeDynamicImport(code, node, dynamicName);
isTouched = true;
this.skip();
}
}, leave(node) {
if (node.scope) {
scope = node.scope.parent;
}
}
});
return isTouched;
}
export default importToGlobals;