/
shake.js
124 lines (105 loc) Β· 3.12 KB
/
shake.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
const t = require('@babel/types');
const EXPORTS_RE = /^\$([^$]+)\$exports$/;
/**
* 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;
}
if (isPure(binding)) {
return binding;
}
if (!EXPORTS_RE.test(name)) {
return null;
}
// Is there any references which aren't simple assignments?
let bailout = binding.referencePaths.some(
path => !isExportAssignment(path) && !isUnusedWildcard(path)
);
if (bailout) {
return null;
} else {
return binding;
}
}
function isPure(binding) {
if (binding.referenced) {
return false;
}
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;
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.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.parentPath.isSequenceExpression() &&
path.parent.expressions.length === 1
) {
remove(path.parentPath);
} else if (!path.removed) {
path.remove();
}
}