UNPKG

6.73 kBJavaScriptView Raw
1import { blank, forOwn, keys } from './utils/object.js';
2import run from './utils/run.js';
3import { SyntheticReference } from './Reference.js';
4
5const use = alias => alias.use();
6
7export default class Declaration {
8 constructor ( node, isParam, statement ) {
9 if ( node ) {
10 if ( node.type === 'FunctionDeclaration' ) {
11 this.isFunctionDeclaration = true;
12 this.functionNode = node;
13 } else if ( node.type === 'VariableDeclarator' && node.init && /FunctionExpression/.test( node.init.type ) ) {
14 this.isFunctionDeclaration = true;
15 this.functionNode = node.init;
16 }
17 }
18
19 this.statement = statement;
20 this.name = null;
21 this.exportName = null;
22 this.isParam = isParam;
23
24 this.isReassigned = false;
25 this.aliases = [];
26
27 this.isUsed = false;
28 }
29
30 addAlias ( declaration ) {
31 this.aliases.push( declaration );
32 }
33
34 addReference ( reference ) {
35 reference.declaration = this;
36 this.name = reference.name; // TODO handle differences of opinion
37
38 if ( reference.isReassignment ) this.isReassigned = true;
39 }
40
41 render ( es6 ) {
42 if ( es6 ) return this.name;
43 if ( !this.isReassigned || !this.exportName ) return this.name;
44
45 return `exports.${this.exportName}`;
46 }
47
48 run ( strongDependencies ) {
49 if ( this.tested ) return this.hasSideEffects;
50
51
52 if ( !this.functionNode ) {
53 this.hasSideEffects = true; // err on the side of caution. TODO handle unambiguous `var x; x = y => z` cases
54 } else {
55 if ( this.running ) return true; // short-circuit infinite loop
56 this.running = true;
57
58 this.hasSideEffects = run( this.functionNode.body, this.functionNode._scope, this.statement, strongDependencies, false );
59
60 this.running = false;
61 }
62
63 this.tested = true;
64 return this.hasSideEffects;
65 }
66
67 use () {
68 if ( this.isUsed ) return;
69
70 this.isUsed = true;
71 if ( this.statement ) this.statement.mark();
72
73 this.aliases.forEach( use );
74 }
75}
76
77export class SyntheticDefaultDeclaration {
78 constructor ( node, statement, name ) {
79 this.node = node;
80 this.statement = statement;
81 this.name = name;
82
83 this.original = null;
84 this.exportName = null;
85 this.aliases = [];
86 }
87
88 addAlias ( declaration ) {
89 this.aliases.push( declaration );
90 }
91
92 addReference ( reference ) {
93 // Bind the reference to `this` declaration.
94 reference.declaration = this;
95
96 // Don't change the name to `default`; it's not a valid identifier name.
97 if ( reference.name === 'default' ) return;
98
99 this.name = reference.name;
100 }
101
102 bind ( declaration ) {
103 this.original = declaration;
104 }
105
106 render () {
107 return !this.original || this.original.isReassigned ?
108 this.name :
109 this.original.render();
110 }
111
112 run ( strongDependencies ) {
113 if ( this.original ) {
114 return this.original.run( strongDependencies );
115 }
116
117 let declaration = this.node.declaration;
118 while ( declaration.type === 'ParenthesizedExpression' ) declaration = declaration.expression;
119
120 if ( /FunctionExpression/.test( declaration.type ) ) {
121 return run( declaration.body, this.statement.scope, this.statement, strongDependencies, false );
122 }
123
124 // otherwise assume the worst
125 return true;
126 }
127
128 use () {
129 this.isUsed = true;
130 this.statement.mark();
131
132 if ( this.original ) this.original.use();
133
134 this.aliases.forEach( use );
135 }
136}
137
138export class SyntheticGlobalDeclaration {
139 constructor ( name ) {
140 this.name = name;
141 this.isExternal = true;
142 this.isGlobal = true;
143 this.isReassigned = false;
144
145 this.aliases = [];
146
147 this.isUsed = false;
148 }
149
150 addAlias ( declaration ) {
151 this.aliases.push( declaration );
152 }
153
154 addReference ( reference ) {
155 reference.declaration = this;
156 if ( reference.isReassignment ) this.isReassigned = true;
157 }
158
159 render () {
160 return this.name;
161 }
162
163 run () {
164 return true;
165 }
166
167 use () {
168 if ( this.isUsed ) return;
169 this.isUsed = true;
170
171 this.aliases.forEach( use );
172 }
173}
174
175export class SyntheticNamespaceDeclaration {
176 constructor ( module ) {
177 this.isNamespace = true;
178 this.module = module;
179 this.name = null;
180
181 this.needsNamespaceBlock = false;
182 this.aliases = [];
183
184 this.originals = blank();
185 module.getExports().forEach( name => {
186 this.originals[ name ] = module.traceExport( name );
187 });
188 }
189
190 addAlias ( declaration ) {
191 this.aliases.push( declaration );
192 }
193
194 addReference ( reference ) {
195 // if we have e.g. `foo.bar`, we can optimise
196 // the reference by pointing directly to `bar`
197 if ( reference.parts.length ) {
198 reference.name = reference.parts.shift();
199
200 reference.end += reference.name.length + 1; // TODO this is brittle
201
202 const original = this.originals[ reference.name ];
203
204 // throw with an informative error message if the reference doesn't exist.
205 if ( !original ) {
206 this.module.bundle.onwarn( `Export '${reference.name}' is not defined by '${this.module.id}'` );
207 reference.isUndefined = true;
208 return;
209 }
210
211 original.addReference( reference );
212 return;
213 }
214
215 // otherwise we're accessing the namespace directly,
216 // which means we need to mark all of this module's
217 // exports and render a namespace block in the bundle
218 if ( !this.needsNamespaceBlock ) {
219 this.needsNamespaceBlock = true;
220 this.module.bundle.internalNamespaces.push( this );
221
222 // add synthetic references, in case of chained
223 // namespace imports
224 forOwn( this.originals, ( original, name ) => {
225 original.addReference( new SyntheticReference( name ) );
226 });
227 }
228
229 reference.declaration = this;
230 this.name = reference.name;
231 }
232
233 renderBlock ( indentString ) {
234 const members = keys( this.originals ).map( name => {
235 const original = this.originals[ name ];
236
237 if ( original.isReassigned ) {
238 return `${indentString}get ${name} () { return ${original.render()}; }`;
239 }
240
241 return `${indentString}${name}: ${original.render()}`;
242 });
243
244 return `var ${this.render()} = Object.freeze({\n${members.join( ',\n' )}\n});\n\n`;
245 }
246
247 render () {
248 return this.name;
249 }
250
251 use () {
252 forOwn( this.originals, use );
253 this.aliases.forEach( use );
254 }
255}
256
257export class ExternalDeclaration {
258 constructor ( module, name ) {
259 this.module = module;
260 this.name = name;
261 this.safeName = null;
262 this.isExternal = true;
263
264 this.isNamespace = name === '*';
265 }
266
267 addAlias () {
268 // noop
269 }
270
271 addReference ( reference ) {
272 reference.declaration = this;
273
274 if ( this.name === 'default' || this.name === '*' ) {
275 this.module.suggestName( reference.name );
276 }
277 }
278
279 render ( es6 ) {
280 if ( this.name === '*' ) {
281 return this.module.name;
282 }
283
284 if ( this.name === 'default' ) {
285 return !es6 && this.module.exportsNames ?
286 `${this.module.name}__default` :
287 this.module.name;
288 }
289
290 return es6 ? this.safeName : `${this.module.name}.${this.name}`;
291 }
292
293 run () {
294 return true;
295 }
296
297 setSafeName ( name ) {
298 this.safeName = name;
299 }
300
301 use () {
302 // noop?
303 }
304}