UNPKG

20.1 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
6
7const path = require('path');
8
9const mm = require('micromatch');
10
11const t = require('@babel/types');
12
13const template = require('@babel/template').default;
14
15const rename = require('./renamer');
16
17const _require = require('./utils'),
18 getName = _require.getName,
19 getIdentifier = _require.getIdentifier,
20 getExportIdentifier = _require.getExportIdentifier;
21
22const WRAPPER_TEMPLATE = template(`
23 var NAME = (function () {
24 var exports = this;
25 var module = {exports: this};
26 BODY;
27 return module.exports;
28 }).call({});
29`);
30const ESMODULE_TEMPLATE = template(`exports.__esModule = true;`);
31const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL');
32const EXPORT_ALL_TEMPLATE = template('$parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE))');
33const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)');
34const REQUIRE_RESOLVE_CALL_TEMPLATE = template('$parcel$require$resolve(ID, SOURCE)');
35const TYPEOF = {
36 module: 'object',
37 require: 'function'
38};
39
40function hasSideEffects(asset, {
41 sideEffects
42} = asset._package) {
43 switch (typeof sideEffects) {
44 case 'undefined':
45 return true;
46
47 case 'boolean':
48 return sideEffects;
49
50 case 'string':
51 return mm.isMatch(path.relative(asset._package.pkgdir, asset.name), sideEffects, {
52 matchBase: true
53 });
54
55 case 'object':
56 return sideEffects.some(sideEffects => hasSideEffects(asset, {
57 sideEffects
58 }));
59 }
60}
61
62module.exports = {
63 Program: {
64 enter(path, asset) {
65 path.scope.crawl();
66 asset.cacheData.imports = asset.cacheData.imports || Object.create(null);
67 asset.cacheData.exports = asset.cacheData.exports || Object.create(null);
68 asset.cacheData.wildcards = asset.cacheData.wildcards || [];
69 asset.cacheData.sideEffects = asset._package && hasSideEffects(asset);
70 let shouldWrap = false;
71 path.traverse({
72 CallExpression(path) {
73 // If we see an `eval` call, wrap the module in a function.
74 // Otherwise, local variables accessed inside the eval won't work.
75 let callee = path.node.callee;
76
77 if (t.isIdentifier(callee) && callee.name === 'eval' && !path.scope.hasBinding('eval', true)) {
78 asset.cacheData.isCommonJS = true;
79 shouldWrap = true;
80 path.stop();
81 }
82 },
83
84 ReturnStatement(path) {
85 // Wrap in a function if we see a top-level return statement.
86 if (!path.getFunctionParent()) {
87 shouldWrap = true;
88 asset.cacheData.isCommonJS = true;
89 path.replaceWith(t.returnStatement(t.memberExpression(t.identifier('module'), t.identifier('exports'))));
90 path.stop();
91 }
92 },
93
94 ReferencedIdentifier(path) {
95 // We must wrap if `module` is referenced as a free identifier rather
96 // than a statically resolvable member expression.
97 if (path.node.name === 'module' && (!path.parentPath.isMemberExpression() || path.parent.computed) && !(path.parentPath.isUnaryExpression() && path.parent.operator === 'typeof') && !path.scope.hasBinding('module') && !path.scope.getData('shouldWrap')) {
98 asset.cacheData.isCommonJS = true;
99 shouldWrap = true;
100 path.stop();
101 }
102 }
103
104 });
105 path.scope.setData('shouldWrap', shouldWrap);
106 },
107
108 exit(path, asset) {
109 let scope = path.scope;
110
111 if (scope.getData('shouldWrap')) {
112 if (asset.cacheData.isES6Module) {
113 path.unshiftContainer('body', [ESMODULE_TEMPLATE()]);
114 }
115
116 path.replaceWith(t.program([WRAPPER_TEMPLATE({
117 NAME: getExportsIdentifier(asset),
118 BODY: path.node.body
119 })]));
120 asset.cacheData.exports = {};
121 asset.cacheData.isCommonJS = true;
122 asset.cacheData.isES6Module = false;
123 } else {
124 // Re-crawl scope so we are sure to have all bindings.
125 scope.crawl(); // Rename each binding in the top-level scope to something unique.
126
127 for (let name in scope.bindings) {
128 if (!name.startsWith('$' + t.toIdentifier(asset.id))) {
129 let newName = getName(asset, 'var', name);
130 rename(scope, name, newName);
131 }
132 }
133
134 let exportsIdentifier = getExportsIdentifier(asset); // Add variable that represents module.exports if it is referenced and not declared.
135
136 if (scope.hasGlobal(exportsIdentifier.name) && !scope.hasBinding(exportsIdentifier.name)) {
137 scope.push({
138 id: exportsIdentifier,
139 init: t.objectExpression([])
140 });
141 }
142 }
143
144 path.stop();
145 asset.isAstDirty = true;
146 }
147
148 },
149
150 DirectiveLiteral(path) {
151 // Remove 'use strict' directives, since modules are concatenated - one strict mode
152 // module should not apply to all other modules in the same scope.
153 if (path.node.value === 'use strict') {
154 path.parentPath.remove();
155 }
156 },
157
158 MemberExpression(path, asset) {
159 if (path.scope.hasBinding('module') || path.scope.getData('shouldWrap')) {
160 return;
161 }
162
163 if (t.matchesPattern(path.node, 'module.exports')) {
164 path.replaceWith(getExportsIdentifier(asset));
165 asset.cacheData.isCommonJS = true;
166 }
167
168 if (t.matchesPattern(path.node, 'module.id')) {
169 path.replaceWith(t.stringLiteral(asset.id));
170 }
171
172 if (t.matchesPattern(path.node, 'module.hot')) {
173 path.replaceWith(t.identifier('null'));
174 }
175
176 if (t.matchesPattern(path.node, 'module.bundle')) {
177 path.replaceWith(t.identifier('require'));
178 }
179 },
180
181 ReferencedIdentifier(path, asset) {
182 if (path.node.name === 'exports' && !path.scope.hasBinding('exports') && !path.scope.getData('shouldWrap')) {
183 path.replaceWith(getExportsIdentifier(asset));
184 asset.cacheData.isCommonJS = true;
185 }
186
187 if (path.node.name === 'global' && !path.scope.hasBinding('global')) {
188 path.replaceWith(t.identifier('$parcel$global'));
189 asset.globals.delete('global');
190 }
191
192 let globalCode = asset.globals.get(path.node.name);
193
194 if (globalCode) {
195 path.scope.getProgramParent().path.unshiftContainer('body', [template(globalCode)()]);
196 asset.globals.delete(path.node.name);
197 }
198 },
199
200 ThisExpression(path, asset) {
201 if (!path.scope.parent && !path.scope.getData('shouldWrap')) {
202 path.replaceWith(getExportsIdentifier(asset));
203 asset.cacheData.isCommonJS = true;
204 }
205 },
206
207 AssignmentExpression(path, asset) {
208 if (path.scope.hasBinding('exports') || path.scope.getData('shouldWrap')) {
209 return;
210 }
211
212 let _path$node = path.node,
213 left = _path$node.left,
214 right = _path$node.right;
215
216 if (t.isIdentifier(left) && left.name === 'exports') {
217 path.get('left').replaceWith(getExportsIdentifier(asset));
218 asset.cacheData.isCommonJS = true;
219 } // If we can statically evaluate the name of a CommonJS export, create an ES6-style export for it.
220 // This allows us to remove the CommonJS export object completely in many cases.
221
222
223 if (t.isMemberExpression(left) && t.isIdentifier(left.object, {
224 name: 'exports'
225 }) && (t.isIdentifier(left.property) && !left.computed || t.isStringLiteral(left.property))) {
226 let name = t.isIdentifier(left.property) ? left.property.name : left.property.value;
227 let identifier = getExportIdentifier(asset, name); // Replace the CommonJS assignment with a reference to the ES6 identifier.
228
229 path.get('left.object').replaceWith(getExportsIdentifier(asset));
230 path.get('right').replaceWith(identifier); // If this is the first assignment, create a binding for the ES6-style export identifier.
231 // Otherwise, assign to the existing export binding.
232
233 let scope = path.scope.getProgramParent();
234
235 if (!scope.hasBinding(identifier.name)) {
236 asset.cacheData.exports[name] = identifier.name;
237
238 let _path$insertBefore = path.insertBefore(t.variableDeclaration('var', [t.variableDeclarator(t.clone(identifier), right)])),
239 _path$insertBefore2 = (0, _slicedToArray2.default)(_path$insertBefore, 1),
240 decl = _path$insertBefore2[0];
241
242 scope.registerDeclaration(decl);
243 } else {
244 path.insertBefore(t.assignmentExpression('=', t.clone(identifier), right));
245 }
246
247 asset.cacheData.isCommonJS = true;
248 }
249 },
250
251 UnaryExpression(path) {
252 // Replace `typeof module` with "object"
253 if (path.node.operator === 'typeof' && t.isIdentifier(path.node.argument) && TYPEOF[path.node.argument.name] && !path.scope.hasBinding(path.node.argument.name) && !path.scope.getData('shouldWrap')) {
254 path.replaceWith(t.stringLiteral(TYPEOF[path.node.argument.name]));
255 }
256 },
257
258 CallExpression(path, asset) {
259 let _path$node2 = path.node,
260 callee = _path$node2.callee,
261 args = _path$node2.arguments;
262 let ignore = args.length !== 1 || !t.isStringLiteral(args[0]) || path.scope.hasBinding('require');
263
264 if (ignore) {
265 return;
266 }
267
268 if (t.isIdentifier(callee, {
269 name: 'require'
270 })) {
271 let source = args[0].value; // Ignore require calls that were ignored earlier.
272
273 if (!asset.dependencies.has(source)) {
274 return;
275 } // If this require call does not occur in the top-level, e.g. in a function
276 // or inside an if statement, or if it might potentially happen conditionally,
277 // the module must be wrapped in a function so that the module execution order is correct.
278
279
280 let parent = path.getStatementParent().parentPath;
281 let bail = path.findParent(p => p.isConditionalExpression() || p.isLogicalExpression() || p.isSequenceExpression());
282
283 if (!parent.isProgram() || bail) {
284 asset.dependencies.get(source).shouldWrap = true;
285 }
286
287 asset.cacheData.imports['$require$' + source] = [source, '*']; // Generate a variable name based on the current asset id and the module name to require.
288 // This will be replaced by the final variable name of the resolved asset in the packager.
289
290 path.replaceWith(REQUIRE_CALL_TEMPLATE({
291 ID: t.stringLiteral(asset.id),
292 SOURCE: t.stringLiteral(args[0].value)
293 }));
294 }
295
296 if (t.matchesPattern(callee, 'require.resolve')) {
297 path.replaceWith(REQUIRE_RESOLVE_CALL_TEMPLATE({
298 ID: t.stringLiteral(asset.id),
299 SOURCE: args[0]
300 }));
301 }
302 },
303
304 ImportDeclaration(path, asset) {
305 // For each specifier, rename the local variables to point to the imported name.
306 // This will be replaced by the final variable name of the resolved asset in the packager.
307 var _iteratorNormalCompletion = true;
308 var _didIteratorError = false;
309 var _iteratorError = undefined;
310
311 try {
312 for (var _iterator = path.node.specifiers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
313 let specifier = _step.value;
314 let id = getIdentifier(asset, 'import', specifier.local.name);
315 rename(path.scope, specifier.local.name, id.name);
316
317 if (t.isImportDefaultSpecifier(specifier)) {
318 asset.cacheData.imports[id.name] = [path.node.source.value, 'default'];
319 } else if (t.isImportSpecifier(specifier)) {
320 asset.cacheData.imports[id.name] = [path.node.source.value, specifier.imported.name];
321 } else if (t.isImportNamespaceSpecifier(specifier)) {
322 asset.cacheData.imports[id.name] = [path.node.source.value, '*'];
323 }
324 }
325 } catch (err) {
326 _didIteratorError = true;
327 _iteratorError = err;
328 } finally {
329 try {
330 if (!_iteratorNormalCompletion && _iterator.return != null) {
331 _iterator.return();
332 }
333 } finally {
334 if (_didIteratorError) {
335 throw _iteratorError;
336 }
337 }
338 }
339
340 addImport(asset, path);
341 path.remove();
342 },
343
344 ExportDefaultDeclaration(path, asset) {
345 let declaration = path.node.declaration;
346 let identifier = getExportIdentifier(asset, 'default');
347 let name = declaration.id ? declaration.id.name : declaration.name;
348
349 if (asset.cacheData.imports[name]) {
350 asset.cacheData.exports['default'] = asset.cacheData.imports[name];
351 identifier = t.identifier(name);
352 }
353
354 if (hasExport(asset, name)) {
355 identifier = t.identifier(name);
356 } // Add assignment to exports object for namespace imports and commonjs.
357
358
359 path.insertAfter(EXPORT_ASSIGN_TEMPLATE({
360 EXPORTS: getExportsIdentifier(asset, path.scope),
361 NAME: t.identifier('default'),
362 LOCAL: t.clone(identifier)
363 }));
364
365 if (t.isIdentifier(declaration)) {
366 // Rename the variable being exported.
367 safeRename(path, asset, declaration.name, identifier.name);
368 path.remove();
369 } else if (t.isExpression(declaration) || !declaration.id) {
370 // Declare a variable to hold the exported value.
371 path.replaceWith(t.variableDeclaration('var', [t.variableDeclarator(identifier, t.toExpression(declaration))]));
372 path.scope.registerDeclaration(path);
373 } else {
374 // Rename the declaration to the exported name.
375 safeRename(path, asset, declaration.id.name, identifier.name);
376 path.replaceWith(declaration);
377 }
378
379 if (!asset.cacheData.exports['default']) {
380 asset.cacheData.exports['default'] = identifier.name;
381 } // Mark the asset as an ES6 module, so we handle imports correctly in the packager.
382
383
384 asset.cacheData.isES6Module = true;
385 },
386
387 ExportNamedDeclaration(path, asset) {
388 let _path$node3 = path.node,
389 declaration = _path$node3.declaration,
390 source = _path$node3.source,
391 specifiers = _path$node3.specifiers;
392
393 if (source) {
394 var _iteratorNormalCompletion2 = true;
395 var _didIteratorError2 = false;
396 var _iteratorError2 = undefined;
397
398 try {
399 for (var _iterator2 = specifiers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
400 let specifier = _step2.value;
401 let exported = specifier.exported;
402
403 if (t.isExportDefaultSpecifier(specifier)) {
404 asset.cacheData.exports[exported.name] = [source.value, 'default'];
405 } else if (t.isExportNamespaceSpecifier(specifier)) {
406 asset.cacheData.exports[exported.name] = [source.value, '*'];
407 } else if (t.isExportSpecifier(specifier)) {
408 asset.cacheData.exports[exported.name] = [source.value, specifier.local.name];
409 }
410
411 let id = getIdentifier(asset, 'import', exported.name);
412 asset.cacheData.imports[id.name] = asset.cacheData.exports[exported.name];
413 path.insertAfter(EXPORT_ASSIGN_TEMPLATE({
414 EXPORTS: getExportsIdentifier(asset, path.scope),
415 NAME: exported,
416 LOCAL: id
417 }));
418 }
419 } catch (err) {
420 _didIteratorError2 = true;
421 _iteratorError2 = err;
422 } finally {
423 try {
424 if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
425 _iterator2.return();
426 }
427 } finally {
428 if (_didIteratorError2) {
429 throw _iteratorError2;
430 }
431 }
432 }
433
434 addImport(asset, path);
435 path.remove();
436 } else if (declaration) {
437 path.replaceWith(declaration);
438 let identifiers = t.isIdentifier(declaration.id) ? [declaration.id] : t.getBindingIdentifiers(declaration);
439
440 for (let id in identifiers) {
441 addExport(asset, path, identifiers[id], identifiers[id]);
442 }
443 } else if (specifiers.length > 0) {
444 var _iteratorNormalCompletion3 = true;
445 var _didIteratorError3 = false;
446 var _iteratorError3 = undefined;
447
448 try {
449 for (var _iterator3 = specifiers[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
450 let specifier = _step3.value;
451 addExport(asset, path, specifier.local, specifier.exported);
452 }
453 } catch (err) {
454 _didIteratorError3 = true;
455 _iteratorError3 = err;
456 } finally {
457 try {
458 if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
459 _iterator3.return();
460 }
461 } finally {
462 if (_didIteratorError3) {
463 throw _iteratorError3;
464 }
465 }
466 }
467
468 path.remove();
469 } // Mark the asset as an ES6 module, so we handle imports correctly in the packager.
470
471
472 asset.cacheData.isES6Module = true;
473 },
474
475 ExportAllDeclaration(path, asset) {
476 asset.cacheData.wildcards.push(path.node.source.value);
477 asset.cacheData.isES6Module = true;
478 path.replaceWith(EXPORT_ALL_TEMPLATE({
479 OLD_NAME: getExportsIdentifier(asset),
480 SOURCE: t.stringLiteral(path.node.source.value),
481 ID: t.stringLiteral(asset.id)
482 }));
483 }
484
485};
486
487function addImport(asset, path) {
488 // Replace with a $parcel$require call so we know where to insert side effects.
489 let requireCall = REQUIRE_CALL_TEMPLATE({
490 ID: t.stringLiteral(asset.id),
491 SOURCE: t.stringLiteral(path.node.source.value)
492 }); // Hoist the call to the top of the file.
493
494 let lastImport = path.scope.getData('hoistedImport');
495
496 if (lastImport) {
497 var _lastImport$insertAft = lastImport.insertAfter(requireCall);
498
499 var _lastImport$insertAft2 = (0, _slicedToArray2.default)(_lastImport$insertAft, 1);
500
501 lastImport = _lastImport$insertAft2[0];
502 } else {
503 var _path$parentPath$unsh = path.parentPath.unshiftContainer('body', [requireCall]);
504
505 var _path$parentPath$unsh2 = (0, _slicedToArray2.default)(_path$parentPath$unsh, 1);
506
507 lastImport = _path$parentPath$unsh2[0];
508 }
509
510 path.scope.setData('hoistedImport', lastImport);
511}
512
513function addExport(asset, path, local, exported) {
514 let scope = path.scope.getProgramParent();
515 let identifier = getExportIdentifier(asset, exported.name);
516
517 if (asset.cacheData.imports[local.name]) {
518 asset.cacheData.exports[exported.name] = asset.cacheData.imports[local.name];
519 identifier = t.identifier(local.name);
520 }
521
522 if (hasExport(asset, local.name)) {
523 identifier = t.identifier(local.name);
524 }
525
526 let assignNode = EXPORT_ASSIGN_TEMPLATE({
527 EXPORTS: getExportsIdentifier(asset, scope),
528 NAME: t.identifier(exported.name),
529 LOCAL: identifier
530 });
531 let binding = scope.getBinding(local.name);
532 let constantViolations = binding ? binding.constantViolations.concat(path) : [path];
533
534 if (!asset.cacheData.exports[exported.name]) {
535 asset.cacheData.exports[exported.name] = identifier.name;
536 }
537
538 try {
539 rename(scope, local.name, identifier.name);
540 } catch (e) {
541 throw new Error('export ' + e.message);
542 }
543
544 constantViolations.forEach(path => path.insertAfter(t.cloneDeep(assignNode)));
545}
546
547function hasExport(asset, name) {
548 let exports = asset.cacheData.exports;
549 return Object.keys(exports).some(k => exports[k] === name);
550}
551
552function safeRename(path, asset, from, to) {
553 if (from === to) {
554 return;
555 } // If the binding that we're renaming is constant, it's safe to rename it.
556 // Otherwise, create a new binding that references the original.
557
558
559 let binding = path.scope.getBinding(from);
560
561 if (binding && binding.constant) {
562 rename(path.scope, from, to);
563 } else {
564 let _path$insertAfter = path.insertAfter(t.variableDeclaration('var', [t.variableDeclarator(t.identifier(to), t.identifier(from))])),
565 _path$insertAfter2 = (0, _slicedToArray2.default)(_path$insertAfter, 1),
566 decl = _path$insertAfter2[0];
567
568 path.scope.getBinding(from).reference(decl.get('declarations.0.init'));
569 path.scope.registerDeclaration(decl);
570 }
571}
572
573function getExportsIdentifier(asset, scope) {
574 if (scope && scope.getData('shouldWrap')) {
575 return t.identifier('exports');
576 } else {
577 return getIdentifier(asset, 'exports');
578 }
579}
\No newline at end of file