UNPKG

5.23 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 resolveGlobal = () => {},
64 name
65}) {
66 code = new MagicString(code);
67 resolveGlobal = createResolveGlobal(resolveGlobal);
68
69 const importBindings = new Map; // name -> [property, source]
70 const exportBindings = new Map;
71 let scope = attachScopes(ast, "scope");
72
73 for (const node of ast.body) {
74 if (node.type === "ImportDeclaration") {
75 analyzeImport(node, importBindings, code);
76 } else if (node.type === "ExportDefaultDeclaration") {
77 analyzeExportDefault(node, exportBindings, code);
78 } else if (node.type === "ExportNamedDeclaration") {
79 analyzeExportNamed(node, exportBindings, code);
80 }
81 }
82
83 const globals = new Set;
84
85 for (const [, source] of importBindings.values()) {
86 globals.add(resolveGlobal(source));
87 }
88
89 walk(ast, {
90 enter(node, parent) {
91 if (/^(import|export)/i.test(node.type)) {
92 this.skip();
93 }
94 if (node.scope) {
95 scope = node.scope;
96 }
97 if (isReference(node, parent)) {
98 if (importBindings.has(node.name) && !scope.contains(node.name)) {
99 overwriteVar(node, parent, getBindingName(importBindings.get(node.name)));
100 } else if (globals.has(node.name) && scope.contains(node.name)) {
101 overwriteVar(node, parent, `_local_${node.name}`);
102 }
103 }
104 },
105 leave(node) {
106 if (node.scope) {
107 scope = node.scope.parent;
108 }
109 }
110 });
111
112 code.appendLeft(
113 ast.body[0].start,
114 `${getPrefix()}(function () {\n`
115 );
116 code.appendRight(
117 ast.body[ast.body.length - 1].end,
118 `\n${getReturn()}})();`
119 );
120
121 return {
122 code: code.toString(),
123 map: sourcemap ? code.generateMap({hires: true}) : null
124 };
125
126 function getReturn() {
127 if (!exportBindings.size) {
128 return "";
129 }
130 if (exportBindings.size === 1 && exportBindings.has("default")) {
131 return `return ${exportBindings.get("default")};\n`;
132 }
133 return `return {\n${
134 [...exportBindings.entries()]
135 .map(([left, right]) => ` ${left}: ${getName(right)}`)
136 .join(",\n")
137 }\n};\n`;
138
139 function getName(name) {
140 if (Array.isArray(name)) {
141 return getBindingName(name);
142 }
143 return name;
144 }
145 }
146
147 function getPrefix() {
148 return exportBindings.size ? `var ${name} = ` : "";
149 }
150
151 function getBindingName([prop, source]) {
152 if (prop === "default") {
153 return resolveGlobal(source);
154 }
155 return `${resolveGlobal(source)}.${prop}`;
156 }
157
158 function overwriteVar(node, parent, name) {
159 if (node.name === name || node.isOverwritten) {
160 return;
161 }
162 if (parent.type === "Property" && parent.key === parent.value) {
163 code.appendLeft(node.end, `: ${name}`);
164 } else {
165 code.overwrite(node.start, node.end, name, {contentOnly: true});
166 }
167 // with object shorthand, the node would be accessed twice (.key and .value)
168 node.isOverwritten = true;
169 }
170
171 function createResolveGlobal(resolveGlobal) {
172 const cache = new Map;
173
174 return name => {
175 if (!cache.has(name)) {
176 cache.set(name, resolveGlobal(name) || camelcase(name));
177 }
178 return cache.get(name);
179 };
180 }
181}
182
183module.exports = {transform};