UNPKG

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