1 | const camelcase = require("camelcase");
|
2 | const MagicString = require("magic-string");
|
3 | const {walk} = require("estree-walker");
|
4 | const isReference = require("is-reference");
|
5 | const {attachScopes} = require("rollup-pluginutils");
|
6 |
|
7 | function 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 |
|
17 | function 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 |
|
37 | function 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 |
|
58 | function 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;
|
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 |
|
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 |
|
183 | module.exports = {transform};
|