1 | import { walk } from 'estree-walker';
|
2 | import MagicString from 'magic-string';
|
3 | import { attachScopes, makeLegalIdentifier } from 'rollup-pluginutils';
|
4 | import { extractNames, flatten, isReference, isTruthy, isFalsy } from './ast-utils.js';
|
5 | import { PREFIX, HELPERS_ID } from './helpers.js';
|
6 | import { getName } from './utils.js';
|
7 |
|
8 | const reserved = 'abstract arguments boolean break byte case catch char class const continue debugger default delete do double else enum eval export extends false final finally float for function goto if implements import in instanceof int interface let long native new null package private protected public return short static super switch synchronized this throw throws transient true try typeof var void volatile while with yield'.split( ' ' );
|
9 | const blacklist = { __esModule: true };
|
10 | reserved.forEach( word => blacklist[ word ] = true );
|
11 |
|
12 | const exportsPattern = /^(?:module\.)?exports(?:\.([a-zA-Z_$][a-zA-Z_$0-9]*))?$/;
|
13 |
|
14 | const firstpassGlobal = /\b(?:require|module|exports|global)\b/;
|
15 | const firstpassNoGlobal = /\b(?:require|module|exports)\b/;
|
16 | const importExportDeclaration = /^(?:Import|Export(?:Named|Default))Declaration/;
|
17 | const functionType = /^(?:FunctionDeclaration|FunctionExpression|ArrowFunctionExpression)$/;
|
18 |
|
19 | function deconflict ( scope, globals, identifier ) {
|
20 | let i = 1;
|
21 | let deconflicted = identifier;
|
22 |
|
23 | while ( scope.contains( deconflicted ) || globals.has( deconflicted ) || deconflicted in blacklist ) deconflicted = `${identifier}_${i++}`;
|
24 | scope.declarations[ deconflicted ] = true;
|
25 |
|
26 | return deconflicted;
|
27 | }
|
28 |
|
29 | function tryParse ( parse, code, id ) {
|
30 | try {
|
31 | return parse( code, { allowReturnOutsideFunction: true });
|
32 | } catch ( err ) {
|
33 | err.message += ` in ${id}`;
|
34 | throw err;
|
35 | }
|
36 | }
|
37 |
|
38 | export function checkFirstpass (code, ignoreGlobal) {
|
39 | const firstpass = ignoreGlobal ? firstpassNoGlobal : firstpassGlobal;
|
40 | return firstpass.test(code);
|
41 | }
|
42 |
|
43 | export function checkEsModule ( parse, code, id ) {
|
44 | const ast = tryParse( parse, code, id );
|
45 |
|
46 |
|
47 | let hasDefaultExport = false;
|
48 | let isEsModule = false;
|
49 | for ( const node of ast.body ) {
|
50 | if ( node.type === 'ExportDefaultDeclaration' )
|
51 | hasDefaultExport = true;
|
52 | if ( importExportDeclaration.test( node.type ) )
|
53 | isEsModule = true;
|
54 | }
|
55 |
|
56 | return { isEsModule, hasDefaultExport, ast };
|
57 | }
|
58 |
|
59 | export function transformCommonjs ( parse, code, id, isEntry, ignoreGlobal, ignoreRequire, customNamedExports, sourceMap, allowDynamicRequire, astCache ) {
|
60 | const ast = astCache || tryParse( parse, code, id );
|
61 |
|
62 | const magicString = new MagicString( code );
|
63 |
|
64 | const required = {};
|
65 |
|
66 |
|
67 | const sources = [];
|
68 |
|
69 | let uid = 0;
|
70 |
|
71 | let scope = attachScopes( ast, 'scope' );
|
72 | const uses = { module: false, exports: false, global: false, require: false };
|
73 |
|
74 | let lexicalDepth = 0;
|
75 | let programDepth = 0;
|
76 |
|
77 | const globals = new Set();
|
78 |
|
79 | const HELPERS_NAME = deconflict( scope, globals, 'commonjsHelpers' );
|
80 |
|
81 | const namedExports = {};
|
82 |
|
83 |
|
84 | let shouldWrap = /__esModule/.test( code );
|
85 |
|
86 | function isRequireStatement ( node ) {
|
87 | if ( !node ) return;
|
88 | if ( node.type !== 'CallExpression' ) return;
|
89 | if ( node.callee.name !== 'require' || scope.contains( 'require' ) ) return;
|
90 | if ( node.arguments.length !== 1 || (node.arguments[0].type !== 'Literal' && (node.arguments[0].type !== 'TemplateLiteral' || node.arguments[0].expressions.length > 0) ) ) return;
|
91 | if ( ignoreRequire( node.arguments[0].value ) ) return;
|
92 |
|
93 | return true;
|
94 | }
|
95 |
|
96 | function getRequired ( node, name ) {
|
97 | const source = node.arguments[0].type === 'Literal' ? node.arguments[0].value : node.arguments[0].quasis[0].value.cooked;
|
98 |
|
99 | const existing = required[ source ];
|
100 | if ( existing === undefined ) {
|
101 | sources.push( source );
|
102 |
|
103 | if ( !name ) {
|
104 | do name = `require$$${uid++}`;
|
105 | while ( scope.contains( name ) );
|
106 | }
|
107 |
|
108 | required[ source ] = { source, name, importsDefault: false };
|
109 | }
|
110 |
|
111 | return required[ source ];
|
112 | }
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | const assignedTo = new Set();
|
118 | walk( ast, {
|
119 | enter ( node ) {
|
120 | if ( node.type !== 'AssignmentExpression' ) return;
|
121 | if ( node.left.type === 'MemberExpression' ) return;
|
122 |
|
123 | extractNames( node.left ).forEach( name => {
|
124 | assignedTo.add( name );
|
125 | });
|
126 | }
|
127 | });
|
128 |
|
129 | walk( ast, {
|
130 | enter ( node, parent ) {
|
131 | if ( sourceMap ) {
|
132 | magicString.addSourcemapLocation( node.start );
|
133 | magicString.addSourcemapLocation( node.end );
|
134 | }
|
135 |
|
136 |
|
137 | if ( parent && ( parent.type === 'IfStatement' || parent.type === 'ConditionalExpression' ) ) {
|
138 | if ( node === parent.consequent && isFalsy( parent.test ) ) return this.skip();
|
139 | if ( node === parent.alternate && isTruthy( parent.test ) ) return this.skip();
|
140 | }
|
141 |
|
142 | if ( node._skip ) return this.skip();
|
143 |
|
144 | programDepth += 1;
|
145 |
|
146 | if ( node.scope ) scope = node.scope;
|
147 | if ( functionType.test( node.type ) ) lexicalDepth += 1;
|
148 |
|
149 |
|
150 | if ( node.type === 'ReturnStatement' && lexicalDepth === 0 ) {
|
151 | shouldWrap = true;
|
152 | }
|
153 |
|
154 |
|
155 | if ( node.type === 'ThisExpression' && lexicalDepth === 0 ) {
|
156 | uses.global = true;
|
157 | if ( !ignoreGlobal ) magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal`, { storeName: true } );
|
158 | return;
|
159 | }
|
160 |
|
161 |
|
162 | if ( node.type === 'UnaryExpression' && node.operator === 'typeof' ) {
|
163 | const flattened = flatten( node.argument );
|
164 | if ( !flattened ) return;
|
165 |
|
166 | if ( scope.contains( flattened.name ) ) return;
|
167 |
|
168 | if ( flattened.keypath === 'module.exports' || flattened.keypath === 'module' || flattened.keypath === 'exports' ) {
|
169 | magicString.overwrite( node.start, node.end, `'object'`, { storeName: false } );
|
170 | }
|
171 | }
|
172 |
|
173 |
|
174 |
|
175 | if ( node.type === 'Identifier' ) {
|
176 | if ( isReference( node, parent ) && !scope.contains( node.name ) ) {
|
177 | if ( node.name in uses ) {
|
178 | if ( node.name === 'require' ) {
|
179 | if ( allowDynamicRequire ) return;
|
180 | magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsRequire`, { storeName: true } );
|
181 | }
|
182 |
|
183 | uses[ node.name ] = true;
|
184 | if ( node.name === 'global' && !ignoreGlobal ) {
|
185 | magicString.overwrite( node.start, node.end, `${HELPERS_NAME}.commonjsGlobal`, { storeName: true } );
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 | if ( node.name === 'module' || node.name === 'exports' ) {
|
191 | shouldWrap = true;
|
192 | }
|
193 | }
|
194 |
|
195 | if ( node.name === 'define' ) {
|
196 | magicString.overwrite( node.start, node.end, 'undefined', { storeName: true } );
|
197 | }
|
198 |
|
199 | globals.add( node.name );
|
200 | }
|
201 |
|
202 | return;
|
203 | }
|
204 |
|
205 |
|
206 | if ( node.type === 'AssignmentExpression' ) {
|
207 | if ( node.left.type !== 'MemberExpression' ) return;
|
208 |
|
209 | const flattened = flatten( node.left );
|
210 | if ( !flattened ) return;
|
211 |
|
212 | if ( scope.contains( flattened.name ) ) return;
|
213 |
|
214 | const match = exportsPattern.exec( flattened.keypath );
|
215 | if ( !match || flattened.keypath === 'exports' ) return;
|
216 |
|
217 | uses[ flattened.name ] = true;
|
218 |
|
219 |
|
220 |
|
221 | if ( programDepth > 3 ) shouldWrap = true;
|
222 |
|
223 | node.left._skip = true;
|
224 |
|
225 | if ( flattened.keypath === 'module.exports' && node.right.type === 'ObjectExpression' ) {
|
226 | return node.right.properties.forEach( prop => {
|
227 | if ( prop.computed || prop.key.type !== 'Identifier' ) return;
|
228 | const name = prop.key.name;
|
229 | if ( name === makeLegalIdentifier( name ) ) namedExports[ name ] = true;
|
230 | });
|
231 | }
|
232 |
|
233 | if ( match[1] ) namedExports[ match[1] ] = true;
|
234 | return;
|
235 | }
|
236 |
|
237 |
|
238 | if ( node.type === 'VariableDeclarator' && node.id.type === 'Identifier' && isRequireStatement( node.init ) ) {
|
239 |
|
240 | if ( scope.parent ) return;
|
241 |
|
242 |
|
243 | if ( assignedTo.has( node.id.name ) ) return;
|
244 |
|
245 | const r = getRequired( node.init, node.id.name );
|
246 | r.importsDefault = true;
|
247 |
|
248 | if ( r.name === node.id.name ) {
|
249 | node._shouldRemove = true;
|
250 | }
|
251 | }
|
252 |
|
253 | if ( !isRequireStatement( node ) ) return;
|
254 |
|
255 | const r = getRequired( node );
|
256 |
|
257 | if ( parent.type === 'ExpressionStatement' ) {
|
258 |
|
259 | magicString.remove( parent.start, parent.end );
|
260 | } else {
|
261 | r.importsDefault = true;
|
262 | magicString.overwrite( node.start, node.end, r.name );
|
263 | }
|
264 |
|
265 | node.callee._skip = true;
|
266 | },
|
267 |
|
268 | leave ( node ) {
|
269 | programDepth -= 1;
|
270 | if ( node.scope ) scope = scope.parent;
|
271 | if ( functionType.test( node.type ) ) lexicalDepth -= 1;
|
272 |
|
273 | if ( node.type === 'VariableDeclaration' ) {
|
274 | let keepDeclaration = false;
|
275 | let c = node.declarations[0].start;
|
276 |
|
277 | for ( let i = 0; i < node.declarations.length; i += 1 ) {
|
278 | const declarator = node.declarations[i];
|
279 |
|
280 | if ( declarator._shouldRemove ) {
|
281 | magicString.remove( c, declarator.end );
|
282 | } else {
|
283 | if ( !keepDeclaration ) {
|
284 | magicString.remove( c, declarator.start );
|
285 | keepDeclaration = true;
|
286 | }
|
287 |
|
288 | c = declarator.end;
|
289 | }
|
290 | }
|
291 |
|
292 | if ( !keepDeclaration ) {
|
293 | magicString.remove( node.start, node.end );
|
294 | }
|
295 | }
|
296 | }
|
297 | });
|
298 |
|
299 | if ( !sources.length && !uses.module && !uses.exports && !uses.require && ( ignoreGlobal || !uses.global ) ) {
|
300 | if ( Object.keys( namedExports ).length ) {
|
301 | throw new Error( `Custom named exports were specified for ${id} but it does not appear to be a CommonJS module` );
|
302 | }
|
303 | return null;
|
304 | }
|
305 |
|
306 | const includeHelpers = shouldWrap || uses.global || uses.require;
|
307 | const importBlock = ( includeHelpers ? [ `import * as ${HELPERS_NAME} from '${HELPERS_ID}';` ] : [] ).concat(
|
308 | sources.map( source => {
|
309 |
|
310 |
|
311 | return `import '${source}';`;
|
312 | }),
|
313 | sources.map( source => {
|
314 | const { name, importsDefault } = required[ source ];
|
315 | return `import ${importsDefault ? `${name} from ` : ``}'${PREFIX}${source}';`;
|
316 | })
|
317 | ).join( '\n' ) + '\n\n';
|
318 |
|
319 | const namedExportDeclarations = [];
|
320 | let wrapperStart = '';
|
321 | let wrapperEnd = '';
|
322 |
|
323 | const moduleName = deconflict( scope, globals, getName( id ) );
|
324 | if ( !isEntry ) {
|
325 | const exportModuleExports = {
|
326 | str: `export { ${moduleName} as __moduleExports };`,
|
327 | name: '__moduleExports'
|
328 | };
|
329 |
|
330 | namedExportDeclarations.push( exportModuleExports );
|
331 | }
|
332 |
|
333 | const name = getName( id );
|
334 |
|
335 | function addExport ( x ) {
|
336 | const deconflicted = deconflict( scope, globals, name );
|
337 |
|
338 | const declaration = deconflicted === name ?
|
339 | `export var ${x} = ${moduleName}.${x};` :
|
340 | `var ${deconflicted} = ${moduleName}.${x};\nexport { ${deconflicted} as ${x} };`;
|
341 |
|
342 | namedExportDeclarations.push({
|
343 | str: declaration,
|
344 | name: x
|
345 | });
|
346 | }
|
347 |
|
348 | if ( customNamedExports ) customNamedExports.forEach( addExport );
|
349 |
|
350 | const defaultExportPropertyAssignments = [];
|
351 | let hasDefaultExport = false;
|
352 |
|
353 | if ( shouldWrap ) {
|
354 | const args = `module${uses.exports ? ', exports' : ''}`;
|
355 |
|
356 | wrapperStart = `var ${moduleName} = ${HELPERS_NAME}.createCommonjsModule(function (${args}) {\n`;
|
357 | wrapperEnd = `\n});`;
|
358 | } else {
|
359 | const names = [];
|
360 |
|
361 | ast.body.forEach( node => {
|
362 | if ( node.type === 'ExpressionStatement' && node.expression.type === 'AssignmentExpression' ) {
|
363 | const left = node.expression.left;
|
364 | const flattened = flatten( left );
|
365 |
|
366 | if ( !flattened ) return;
|
367 |
|
368 | const match = exportsPattern.exec( flattened.keypath );
|
369 | if ( !match ) return;
|
370 |
|
371 | if ( flattened.keypath === 'module.exports' ) {
|
372 | hasDefaultExport = true;
|
373 | magicString.overwrite( left.start, left.end, `var ${moduleName}` );
|
374 | } else {
|
375 | const name = match[1];
|
376 | const deconflicted = deconflict( scope, globals, name );
|
377 |
|
378 | names.push({ name, deconflicted });
|
379 |
|
380 | magicString.overwrite( node.start, left.end, `var ${deconflicted}` );
|
381 |
|
382 | const declaration = name === deconflicted ?
|
383 | `export { ${name} };` :
|
384 | `export { ${deconflicted} as ${name} };`;
|
385 |
|
386 | if ( name !== 'default' ) {
|
387 | namedExportDeclarations.push({
|
388 | str: declaration,
|
389 | name
|
390 | });
|
391 | delete namedExports[name];
|
392 | }
|
393 |
|
394 | defaultExportPropertyAssignments.push( `${moduleName}.${name} = ${deconflicted};` );
|
395 | }
|
396 | }
|
397 | });
|
398 |
|
399 | if ( !hasDefaultExport ) {
|
400 | wrapperEnd = `\n\nvar ${moduleName} = {\n${
|
401 | names.map( ({ name, deconflicted }) => `\t${name}: ${deconflicted}` ).join( ',\n' )
|
402 | }\n};`;
|
403 | }
|
404 | }
|
405 | Object.keys( namedExports )
|
406 | .filter( key => !blacklist[ key ] )
|
407 | .forEach( addExport );
|
408 |
|
409 | const defaultExport = /__esModule/.test( code ) ?
|
410 | `export default ${HELPERS_NAME}.unwrapExports(${moduleName});` :
|
411 | `export default ${moduleName};`;
|
412 |
|
413 | const named = namedExportDeclarations
|
414 | .filter( x => x.name !== 'default' || !hasDefaultExport )
|
415 | .map( x => x.str );
|
416 |
|
417 | const exportBlock = '\n\n' + [ defaultExport ]
|
418 | .concat( named )
|
419 | .concat( hasDefaultExport ? defaultExportPropertyAssignments : [] )
|
420 | .join( '\n' );
|
421 |
|
422 | magicString.trim()
|
423 | .prepend( importBlock + wrapperStart )
|
424 | .trim()
|
425 | .append( wrapperEnd + exportBlock );
|
426 |
|
427 | code = magicString.toString();
|
428 | const map = sourceMap ? magicString.generateMap() : null;
|
429 |
|
430 | return { code, map };
|
431 | }
|