UNPKG

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