Recursively traverse every time we strip a block

Summary:
It turns out that block removal can create dead variables, which can in turn further process and improve other block removal. This diff takes care of ensuring that this process happens iteratively until no further blocks can be removed.

It also moves block removal to `Program.exit`. This is important to avoid removing "unused" blocks, which are non-atomically added by other plugins (e.g. Babel's class plugin first adds a `function` that will later be used as a helper, but the dead code removal was kicked-in before adding its usage, and was getting removed).

Reviewed By: davidaurelio

Differential Revision: D8069299

fbshipit-source-id: d86c9be2a76fced9a6529a1d5aaaad4430d0f194
This commit is contained in:
Miguel Jimenez Esun 2018-05-21 07:52:24 -07:00 committed by Facebook Github Bot
parent 263118c84b
commit 5841122d27
2 changed files with 52 additions and 19 deletions

View File

@ -143,7 +143,7 @@ describe('constant expressions', () => {
var a = 'b';
}
`;
expect(fold('arbitrary.js', code)).toEqual('{var a=3;var b=a+4;}');
expect(fold('arbitrary.js', code)).toEqual('{var a=3;var b=7;}');
});
it('can optimize nested if-else constructs', () => {
@ -235,6 +235,18 @@ describe('constant expressions', () => {
);
});
it('recursively strips off functions', () => {
const code = `
function x() {}
if (false) {
x();
}
`;
expect(fold('arbitrary.js', code)).toEqual('');
});
it('verifies that mixes of variables and functions properly minifies', () => {
const code = `
var x = 2;

View File

@ -16,23 +16,25 @@ function constantFoldingPlugin(context: {types: BabelTypes}) {
const t = context.types;
const FunctionDeclaration = {
exit(path: Object) {
exit(path: Object, state: Object) {
const binding = path.scope.getBinding(path.node.id.name);
if (binding && !binding.referenced) {
state.stripped = true;
path.remove();
}
},
};
const FunctionExpression = {
exit(path: Object) {
exit(path: Object, state: Object) {
const parentPath = path.parentPath;
if (t.isVariableDeclarator(parentPath)) {
const binding = parentPath.scope.getBinding(parentPath.node.id.name);
if (binding && !binding.referenced) {
state.stripped = true;
parentPath.remove();
}
}
@ -40,11 +42,13 @@ function constantFoldingPlugin(context: {types: BabelTypes}) {
};
const Conditional = {
exit(path: Object) {
exit(path: Object, state: Object) {
const node = path.node;
const result = path.get('test').evaluate();
if (result.confident) {
state.stripped = true;
if (result.value || node.alternate) {
path.replaceWith(result.value ? node.consequent : node.alternate);
} else if (!result.value) {
@ -90,25 +94,42 @@ function constantFoldingPlugin(context: {types: BabelTypes}) {
};
const Program = {
exit(path: Object) {
path.traverse({
ArrowFunctionExpression: FunctionExpression,
FunctionDeclaration,
FunctionExpression,
});
enter(path: Object, state: Object) {
state.stripped = false;
},
exit(path: Object, state: Object) {
path.traverse(
{
ArrowFunctionExpression: FunctionExpression,
ConditionalExpression: Conditional,
FunctionDeclaration,
FunctionExpression,
IfStatement: Conditional,
},
state,
);
if (state.stripped) {
path.scope.crawl();
// Re-traverse all program, if we removed any blocks. Manually re-call
// enter and exit, because traversing a Program node won't call them.
Program.enter(path, state);
path.traverse(visitor);
Program.exit(path, state);
}
},
};
return {
visitor: {
BinaryExpression: Expression,
ConditionalExpression: Conditional,
IfStatement: Conditional,
LogicalExpression,
Program,
UnaryExpression: Expression,
},
const visitor = {
BinaryExpression: Expression,
LogicalExpression,
Program: {...Program}, // Babel mutates objects passed.
UnaryExpression: Expression,
};
return {visitor};
}
module.exports = constantFoldingPlugin;