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 | strict = false,
|
64 | resolveGlobal = () => {},
|
65 | name
|
66 | }) {
|
67 | code = new MagicString(code);
|
68 | resolveGlobal = createResolveGlobal(resolveGlobal);
|
69 |
|
70 | const importBindings = new Map;
|
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 |
|
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 |
|
186 | module.exports = {transform};
|