115 lines
No EOL
3.4 KiB
JavaScript
115 lines
No EOL
3.4 KiB
JavaScript
"use strict";
|
|
|
|
const t = require('@babel/types');
|
|
/**
|
|
* This is a small small implementation of dead code removal specialized to handle
|
|
* removing unused exports. All other dead code removal happens in workers on each
|
|
* individual file by babel-minify.
|
|
*/
|
|
|
|
|
|
function treeShake(scope) {
|
|
// Keep passing over all bindings in the scope until we don't remove any.
|
|
// This handles cases where we remove one binding which had a reference to
|
|
// another one. That one will get removed in the next pass if it is now unreferenced.
|
|
let removed;
|
|
|
|
do {
|
|
removed = false; // Recrawl to get all bindings.
|
|
|
|
scope.crawl();
|
|
Object.keys(scope.bindings).forEach(name => {
|
|
let binding = getUnusedBinding(scope.path, name); // If it is not safe to remove the binding don't touch it.
|
|
|
|
if (!binding) {
|
|
return;
|
|
} // Remove the binding and all references to it.
|
|
|
|
|
|
binding.path.remove();
|
|
binding.referencePaths.concat(binding.constantViolations).forEach(remove);
|
|
scope.removeBinding(name);
|
|
removed = true;
|
|
});
|
|
} while (removed);
|
|
}
|
|
|
|
module.exports = treeShake; // Check if a binding is safe to remove and returns it if it is.
|
|
|
|
function getUnusedBinding(path, name) {
|
|
let binding = path.scope.getBinding(name);
|
|
|
|
if (!binding) {
|
|
return null;
|
|
}
|
|
|
|
let pure = isPure(binding);
|
|
|
|
if (!binding.referenced && pure) {
|
|
return binding;
|
|
} // Is there any references which aren't simple assignments?
|
|
|
|
|
|
let bailout = binding.referencePaths.some(path => !isExportAssignment(path) && !isUnusedWildcard(path));
|
|
|
|
if (!bailout && pure) {
|
|
return binding;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function isPure(binding) {
|
|
if (binding.path.isVariableDeclarator() && binding.path.get('id').isIdentifier()) {
|
|
let init = binding.path.get('init');
|
|
return init.isPure() || init.isIdentifier() || init.isThisExpression();
|
|
}
|
|
|
|
return binding.path.isPure();
|
|
}
|
|
|
|
function isExportAssignment(path) {
|
|
return (// match "path.any = any;"
|
|
path.parentPath.isMemberExpression() && path.parentPath.parentPath.isAssignmentExpression() && path.parentPath.parentPath.node.left === path.parentPath.node
|
|
);
|
|
}
|
|
|
|
function isUnusedWildcard(path) {
|
|
let parent = path.parent;
|
|
return (// match `$parcel$exportWildcard` calls
|
|
t.isCallExpression(parent) && t.isIdentifier(parent.callee, {
|
|
name: '$parcel$exportWildcard'
|
|
}) && parent.arguments[0] === path.node && // check if the $id$exports variable is used
|
|
!getUnusedBinding(path, parent.arguments[1].name)
|
|
);
|
|
}
|
|
|
|
function remove(path) {
|
|
if (path.isAssignmentExpression()) {
|
|
if (path.parentPath.isSequenceExpression()) {
|
|
if (path.parent.expressions.length == 1) {
|
|
// replace sequence expression with it's sole child
|
|
path.parentPath.replaceWith(path);
|
|
remove(path.parentPath);
|
|
} else {
|
|
path.remove();
|
|
}
|
|
} else if (!path.parentPath.isExpressionStatement()) {
|
|
path.replaceWith(path.node.right);
|
|
} else {
|
|
path.remove();
|
|
}
|
|
} else if (isExportAssignment(path)) {
|
|
remove(path.parentPath.parentPath);
|
|
} else if (isUnusedWildcard(path)) {
|
|
remove(path.parentPath);
|
|
} else if (!path.removed) {
|
|
if (path.parentPath.isSequenceExpression() && path.parent.expressions.length === 1) {
|
|
// replace sequence expression with it's sole child
|
|
path.parentPath.replaceWith(path);
|
|
remove(path.parentPath);
|
|
} else {
|
|
path.remove();
|
|
}
|
|
}
|
|
} |