UNPKG

5.38 kBJavaScriptView Raw
1const camelcase = require("camelcase");
2const MagicString = require("magic-string");
3const {walk} = require("estree-walker");
4const isReference = require("is-reference");
5const {attachScopes} = require("@rollup/pluginutils");
6
7function analyzeImport(node, importBindings, code) {
8 code.remove(node.start, node.end);
9 for (const spec of node.specifiers) {
10 importBindings.set(spec.local.name, [
11 spec.imported ? spec.imported.name : "default",
12 node.source.value
13 ]);
14 }
15}
16
17function analyzeExportDefault(node, exportBindings, code) {
18 if (node.declaration.type === "Identifier") {
19 exportBindings.set("default", node.declaration.name);
20 code.remove(node.start, node.end);
21 } else if (
22 node.declaration.id && (
23 node.declaration.type === "ClassDeclaration" ||
24 node.declaration.type === "FunctionDeclaration"
25 )
26 ) {
27 exportBindings.set("default", node.declaration.id.name);
28 code.remove(node.start, node.declaration.start);
29 } else {
30 exportBindings.set("default", "_iife_default");
31 code.overwrite(node.start, node.declaration.start, "var _iife_default = ", {
32 contentOnly: true
33 });
34 }
35}
36
37function analyzeExportNamed(node, exportBindings, code) {
38 if (!node.declaration) {
39 for (const spec of node.specifiers) {
40 exportBindings.set(
41 spec.exported.name, node.source ?
42 [spec.local.name, node.source.value] : spec.local.name
43 );
44 }
45 code.remove(node.start, node.end);
46 } else {
47 if (node.declaration.type === "VariableDeclaration") {
48 for (const dec of node.declaration.declarations) {
49 exportBindings.set(dec.id.name, dec.id.name);
50 }
51 } else {
52 exportBindings.set(node.declaration.id.name, node.declaration.id.name);
53 }
54 code.remove(node.start, node.declaration.start);
55 }
56}
57
58function transform({
59 code,
60 parse,
61 ast = parse(code),
62 sourcemap = false,
63 strict = false,
64 resolveGlobal = () => {},
65 name
66}) {
67 code = new MagicString(code);
68 resolveGlobal = createResolveGlobal(resolveGlobal);
69
70 const importBindings = new Map; // name -> [property, source]
71 const exportBindings = new Map;
72 let scope = attachScopes(ast, "scope");
73
74 for (const node of ast.body) {
75 if (node.type === "ImportDeclaration") {
76 analyzeImport(node, importBindings, code);
77 } else if (node.type === "ExportDefaultDeclaration") {
78 analyzeExportDefault(node, exportBindings, code);
79 } else if (node.type === "ExportNamedDeclaration") {
80 analyzeExportNamed(node, exportBindings, code);
81 }
82 }
83
84 const globals = new Set;
85
86 for (const [, source] of importBindings.values()) {
87 globals.add(resolveGlobal(source));
88 }
89
90 walk(ast, {
91 enter(node, parent) {
92 if (/^(import|export)/i.test(node.type)) {
93 this.skip();
94 }
95 if (node.scope) {
96 scope = node.scope;
97 }
98 if (isReference(node, parent)) {
99 if (importBindings.has(node.name) && !scope.contains(node.name)) {
100 overwriteVar(node, parent, getBindingName(importBindings.get(node.name)));
101 } else if (globals.has(node.name) && scope.contains(node.name)) {
102 overwriteVar(node, parent, `_local_${node.name}`);
103 }
104 }
105 },
106 leave(node) {
107 if (node.scope) {
108 scope = node.scope.parent;
109 }
110 }
111 });
112
113 code.appendLeft(
114 ast.body[0].start,
115 `${getPrefix()}(function () {\n${strict ? "'use strict';\n" : ""}`
116 );
117 code.appendRight(
118 ast.body[ast.body.length - 1].end,
119 `\n${getReturn()}})();`
120 );
121
122 return {
123 code: code.toString(),
124 map: sourcemap ? code.generateMap({hires: true}) : null
125 };
126
127 function getReturn() {
128 if (!exportBindings.size) {
129 return "";
130 }
131 if (exportBindings.size === 1 && exportBindings.has("default")) {
132 return `return ${exportBindings.get("default")};\n`;
133 }
134 return `return {\n${
135 [...exportBindings.entries()]
136 .map(([left, right]) => ` ${left}: ${getName(right)}`)
137 .join(",\n")
138 }\n};\n`;
139
140 function getName(name) {
141 if (Array.isArray(name)) {
142 return getBindingName(name);
143 }
144 return name;
145 }
146 }
147
148 function getPrefix() {
149 return exportBindings.size ? `var ${name} = ` : "";
150 }
151
152 function getBindingName([prop, source]) {
153 if (prop === "default") {
154 return resolveGlobal(source);
155 }
156 return `${resolveGlobal(source)}.${prop}`;
157 }
158
159 function overwriteVar(node, parent, name) {
160 if (node.name === name || node.isOverwritten) {
161 return;
162 }
163 if (parent.type === "Property" && parent.key.start === parent.value.start) {
164 code.appendLeft(node.end, `: ${name}`);
165 parent.key.isOverwritten = true;
166 parent.value.isOverwritten = true;
167 } else {
168 code.overwrite(node.start, node.end, name, {contentOnly: true});
169 // with object shorthand, the node would be accessed twice (.key and .value)
170 node.isOverwritten = true;
171 }
172 }
173
174 function createResolveGlobal(resolveGlobal) {
175 const cache = new Map;
176
177 return name => {
178 if (!cache.has(name)) {
179 cache.set(name, resolveGlobal(name) || camelcase(name));
180 }
181 return cache.get(name);
182 };
183 }
184}
185
186module.exports = {transform};