UNPKG

5.02 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, "const _iife_default = ", {contentOnly: true});
32 }
33}
34
35function analyzeExportNamed(node, exportBindings, code) {
36 if (!node.declaration) {
37 for (const spec of node.specifiers) {
38 exportBindings.set(spec.exported.name, spec.local.name);
39 }
40 code.remove(node.start, node.end);
41 } else {
42 if (node.declaration.type === "VariableDeclaration") {
43 for (const dec of node.declaration.declarations) {
44 exportBindings.set(dec.id.name, dec.id.name);
45 }
46 } else {
47 exportBindings.set(node.declaration.id.name, node.declaration.id.name);
48 }
49 code.remove(node.start, node.declaration.start);
50 }
51}
52
53function transform({
54 code,
55 parse,
56 ast = parse(code),
57 sourcemap = false,
58 resolveGlobal = () => {},
59 name
60}) {
61 code = new MagicString(code);
62 resolveGlobal = createResolveGlobal(resolveGlobal);
63
64 const importBindings = new Map; // name -> [property, source]
65 const exportBindings = new Map;
66 let scope = attachScopes(ast, "scope");
67
68 for (const node of ast.body) {
69 if (node.type === "ImportDeclaration") {
70 analyzeImport(node, importBindings, code);
71 } else if (node.type === "ExportDefaultDeclaration") {
72 analyzeExportDefault(node, exportBindings, code);
73 } else if (node.type === "ExportNamedDeclaration") {
74 analyzeExportNamed(node, exportBindings, code);
75 }
76 }
77
78 const globals = new Set;
79
80 for (const [, source] of importBindings.values()) {
81 globals.add(resolveGlobal(source));
82 }
83
84 walk(ast, {
85 enter(node, parent) {
86 if (/^(import|export)/i.test(node.type)) {
87 this.skip();
88 }
89 if (node.scope) {
90 scope = node.scope;
91 }
92 if (isReference(node, parent)) {
93 if (importBindings.has(node.name) && !scope.contains(node.name)) {
94 overwriteVar(node, parent, getBindingName(importBindings.get(node.name)));
95 } else if (globals.has(node.name) && scope.contains(node.name)) {
96 overwriteVar(node, parent, `_local_${node.name}`);
97 }
98 }
99 },
100 leave(node) {
101 if (node.scope) {
102 scope = node.scope.parent;
103 }
104 }
105 });
106
107 code.appendLeft(
108 ast.body[0].start,
109 `${getPrefix()}(function () {\n`
110 );
111 code.appendRight(
112 ast.body[ast.body.length - 1].end,
113 `\n${getReturn()}})();`
114 );
115
116 return {
117 code: code.toString(),
118 map: sourcemap ? code.generateMap({hires: true}) : null
119 };
120
121 function getReturn() {
122 if (!exportBindings.size) {
123 return "";
124 }
125 if (exportBindings.size === 1 && exportBindings.has("default")) {
126 return `return ${exportBindings.get("default")};\n`;
127 }
128 return `return {\n${
129 [...exportBindings.entries()]
130 .map(([left, right]) => ` ${left}: ${right}`)
131 .join(",\n")
132 }\n};\n`;
133 }
134
135 function getPrefix() {
136 return exportBindings.size ? `var ${name} = ` : "";
137 }
138
139 function getBindingName([prop, source]) {
140 if (prop === "default") {
141 return resolveGlobal(source);
142 }
143 return `${resolveGlobal(source)}.${prop}`;
144 }
145
146 function overwriteVar(node, parent, name) {
147 if (node.name === name || node.isOverwritten) {
148 return;
149 }
150 if (parent.type === "Property" && parent.key === parent.value) {
151 code.appendLeft(node.end, `: ${name}`);
152 } else {
153 code.overwrite(node.start, node.end, name, {contentOnly: true});
154 }
155 // with object shorthand, the node would be accessed twice (.key and .value)
156 node.isOverwritten = true;
157 }
158
159 function createResolveGlobal(resolveGlobal) {
160 const cache = new Map;
161
162 return name => {
163 if (!cache.has(name)) {
164 cache.set(name, resolveGlobal(name) || camelcase(name));
165 }
166 return cache.get(name);
167 };
168 }
169}
170
171module.exports = {transform};