1 | const {attachScopes, makeLegalIdentifier} = require("@rollup/pluginutils");
|
2 |
|
3 | let walk, isReference;
|
4 |
|
5 | async function prepare() {
|
6 | [{walk}, {default: isReference}] = await Promise.all([
|
7 | import("estree-walker"),
|
8 | import("is-reference")
|
9 | ]);
|
10 | }
|
11 |
|
12 | function 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 |
|
28 | function makeGlobalName(prop, name) {
|
29 | if (prop === "default") {
|
30 | return name;
|
31 | }
|
32 | return `${name}.${prop}`;
|
33 | }
|
34 |
|
35 | function writeSpecLocal(code, root, spec, name, tempNames) {
|
36 | if (spec.isOverwritten) return;
|
37 |
|
38 |
|
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.start === spec.exported.start && spec.local.end === spec.exported.end) {
|
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 |
|
55 | function writeIdentifier(code, node, parent, name) {
|
56 | if (node.name === name || node.isOverwritten) {
|
57 | return;
|
58 | }
|
59 |
|
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 |
|
67 | node.isOverwritten = true;
|
68 | }
|
69 | }
|
70 |
|
71 | function 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 |
|
91 | function writeDynamicImport(code, node, content) {
|
92 | code.overwrite(node.start, node.end, content);
|
93 | }
|
94 |
|
95 | function 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 |
|
104 | async 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 |
|
163 | module.exports = importToGlobals;
|