UNPKG

4.81 kBJavaScriptView Raw
1const {attachScopes, makeLegalIdentifier} = require("@rollup/pluginutils");
2
3let walk, isReference;
4
5async function prepare() {
6 [{walk}, {default: isReference}] = await Promise.all([
7 import("estree-walker"),
8 import("is-reference")
9 ]);
10}
11
12function analyzeImport(node, importBindings, code, getName, globals) {
13 const name = node.source.value && getName(node.source.value);
14 if (!name) {
15 return false;
16 }
17 globals.add(name);
18 for (const spec of node.specifiers) {
19 importBindings.set(spec.local.name, makeGlobalName(
20 spec.imported ? spec.imported.name : "default",
21 name
22 ));
23 }
24 code.remove(node.start, node.end);
25 return true;
26}
27
28function makeGlobalName(prop, name) {
29 if (prop === "default") {
30 return name;
31 }
32 return `${name}.${prop}`;
33}
34
35function writeSpecLocal(code, root, spec, name, tempNames) {
36 if (spec.isOverwritten) return;
37 // we always need an extra assignment for named export statement
38 // https://github.com/eight04/rollup-plugin-external-globals/issues/19
39 const localName = `_global_${makeLegalIdentifier(name)}`;
40 if (!tempNames.has(localName)) {
41 code.appendRight(root.start, `const ${localName} = ${name};\n`);
42 tempNames.add(localName);
43 }
44 if (spec.local.name === localName) {
45 return;
46 }
47 if (spec.local === spec.exported) {
48 code.appendRight(spec.local.start, `${localName} as `);
49 } else {
50 code.overwrite(spec.local.start, spec.local.end, localName);
51 }
52 spec.isOverwritten = true;
53}
54
55function writeIdentifier(code, node, parent, name) {
56 if (node.name === name || node.isOverwritten) {
57 return;
58 }
59 // 2020/8/14, parent.key and parent.value is no longer the same object. However, the shape is the same.
60 if (parent.type === "Property" && parent.key.start === parent.value.start) {
61 code.appendLeft(node.end, `: ${name}`);
62 parent.key.isOverwritten = true;
63 parent.value.isOverwritten = true;
64 } else {
65 code.overwrite(node.start, node.end, name, {contentOnly: true});
66 // FIXME: do we need this?
67 node.isOverwritten = true;
68 }
69}
70
71function analyzeExportNamed(node, code, getName, tempNames) {
72 if (node.declaration || !node.source || !node.source.value) {
73 return false;
74 }
75 const name = getName(node.source.value);
76 if (!name) {
77 return false;
78 }
79 for (const spec of node.specifiers) {
80 const globalName = makeGlobalName(spec.local.name, name);
81 writeSpecLocal(code, node, spec, globalName, tempNames);
82 }
83 if (node.specifiers.length) {
84 code.overwrite(node.specifiers[node.specifiers.length - 1].end, node.source.end, "}");
85 } else {
86 code.remove(node.start, node.end);
87 }
88 return true;
89}
90
91function writeDynamicImport(code, node, content) {
92 code.overwrite(node.start, node.end, content);
93}
94
95function getDynamicImportSource(node) {
96 if (node.type === "ImportExpression") {
97 return node.source.value;
98 }
99 if (node.type === "CallExpression" && node.callee.type === "Import") {
100 return node.arguments[0].value;
101 }
102}
103
104async function importToGlobals({ast, code, getName, getDynamicWrapper}) {
105 await prepare();
106 let scope = attachScopes(ast, "scope");
107 const bindings = new Map;
108 const globals = new Set;
109 let isTouched = false;
110 const tempNames = new Set;
111
112 for (const node of ast.body) {
113 if (node.type === "ImportDeclaration") {
114 isTouched = analyzeImport(node, bindings, code, getName, globals) || isTouched;
115 } else if (node.type === "ExportNamedDeclaration") {
116 isTouched = analyzeExportNamed(node, code, getName, tempNames) || isTouched;
117 }
118 }
119
120 let topStatement;
121 walk(ast, {
122 enter(node, parent) {
123 if (parent && parent.type === "Program") {
124 topStatement = node;
125 }
126 if (/^importdec/i.test(node.type)) {
127 this.skip();
128 return;
129 }
130 if (node.scope) {
131 scope = node.scope;
132 }
133 if (isReference(node, parent)) {
134 if (bindings.has(node.name) && !scope.contains(node.name)) {
135 if (parent.type === "ExportSpecifier") {
136 writeSpecLocal(code, topStatement, parent, bindings.get(node.name), tempNames);
137 } else {
138 writeIdentifier(code, node, parent, bindings.get(node.name));
139 }
140 } else if (globals.has(node.name) && scope.contains(node.name)) {
141 writeIdentifier(code, node, parent, `_local_${node.name}`);
142 }
143 }
144 const source = getDynamicImportSource(node);
145 const name = source && getName(source);
146 const dynamicName = name && getDynamicWrapper(name);
147 if (dynamicName) {
148 writeDynamicImport(code, node, dynamicName);
149 isTouched = true;
150 this.skip();
151 }
152 },
153 leave(node) {
154 if (node.scope) {
155 scope = node.scope.parent;
156 }
157 }
158 });
159
160 return isTouched;
161}
162
163module.exports = importToGlobals;