mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
perf(minifier): merge Normalize and RemoveSyntax pass (#8467)
This commit is contained in:
parent
8accfefa74
commit
8fc238ac34
5 changed files with 92 additions and 136 deletions
|
|
@ -13,7 +13,6 @@ mod peephole_minimize_conditions;
|
|||
mod peephole_remove_dead_code;
|
||||
mod peephole_replace_known_methods;
|
||||
mod peephole_substitute_alternate_syntax;
|
||||
mod remove_syntax;
|
||||
mod remove_unused_code;
|
||||
mod statement_fusion;
|
||||
|
||||
|
|
@ -27,7 +26,6 @@ pub use peephole_minimize_conditions::PeepholeMinimizeConditions;
|
|||
pub use peephole_remove_dead_code::PeepholeRemoveDeadCode;
|
||||
pub use peephole_replace_known_methods::PeepholeReplaceKnownMethods;
|
||||
pub use peephole_substitute_alternate_syntax::PeepholeSubstituteAlternateSyntax;
|
||||
pub use remove_syntax::RemoveSyntax;
|
||||
#[expect(unused)]
|
||||
pub use remove_unused_code::RemoveUnusedCode;
|
||||
pub use statement_fusion::StatementFusion;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,31 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::ConstantEvaluation;
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_syntax::scope::ScopeFlags;
|
||||
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||
|
||||
use crate::{ctx::Ctx, CompressorPass};
|
||||
use crate::{ctx::Ctx, CompressOptions, CompressorPass};
|
||||
|
||||
/// Normalize AST
|
||||
///
|
||||
/// Make subsequent AST passes easier to analyze:
|
||||
///
|
||||
/// * remove `Statement::EmptyStatement`
|
||||
/// * remove `ParenthesizedExpression`
|
||||
/// * convert whiles to fors
|
||||
/// * convert `Infinity` to `f64::INFINITY`
|
||||
/// * convert `NaN` to `f64::NaN`
|
||||
/// * convert `var x; void x` to `void 0`
|
||||
///
|
||||
/// Also
|
||||
///
|
||||
/// * remove `debugger` and `console.log` (optional)
|
||||
///
|
||||
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/Normalize.java>
|
||||
pub struct Normalize;
|
||||
pub struct Normalize {
|
||||
options: CompressOptions,
|
||||
}
|
||||
|
||||
impl<'a> CompressorPass<'a> for Normalize {
|
||||
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
|
||||
|
|
@ -25,6 +34,14 @@ impl<'a> CompressorPass<'a> for Normalize {
|
|||
}
|
||||
|
||||
impl<'a> Traverse<'a> for Normalize {
|
||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) {
|
||||
stmts.retain(|stmt| {
|
||||
!(matches!(stmt, Statement::EmptyStatement(_))
|
||||
|| self.drop_debugger(stmt)
|
||||
|| self.drop_console(stmt))
|
||||
});
|
||||
}
|
||||
|
||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
match stmt {
|
||||
Statement::WhileStatement(_) => {
|
||||
|
|
@ -36,6 +53,9 @@ impl<'a> Traverse<'a> for Normalize {
|
|||
}
|
||||
|
||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Expression::ParenthesizedExpression(paren_expr) = expr {
|
||||
*expr = ctx.ast.move_expression(&mut paren_expr.expression);
|
||||
}
|
||||
match expr {
|
||||
Expression::Identifier(_) => {
|
||||
Self::convert_infinity_or_nan_into_number(expr, ctx);
|
||||
|
|
@ -43,14 +63,53 @@ impl<'a> Traverse<'a> for Normalize {
|
|||
Expression::UnaryExpression(e) if e.operator.is_void() => {
|
||||
Self::convert_void_ident(e, ctx);
|
||||
}
|
||||
Expression::ArrowFunctionExpression(e) => {
|
||||
self.recover_arrow_expression_after_drop_console(e);
|
||||
}
|
||||
Expression::CallExpression(_) if self.options.drop_console => {
|
||||
self.compress_console(expr, ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Normalize {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
pub fn new(options: CompressOptions) -> Self {
|
||||
Self { options }
|
||||
}
|
||||
|
||||
/// Drop `drop_debugger` statement.
|
||||
///
|
||||
/// Enabled by `compress.drop_debugger`
|
||||
fn drop_debugger(&mut self, stmt: &Statement<'a>) -> bool {
|
||||
matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger
|
||||
}
|
||||
|
||||
fn compress_console(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
debug_assert!(self.options.drop_console);
|
||||
if Self::is_console(expr) {
|
||||
*expr = ctx.ast.void_0(expr.span());
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_console(&mut self, stmt: &Statement<'a>) -> bool {
|
||||
self.options.drop_console
|
||||
&& matches!(stmt, Statement::ExpressionStatement(expr) if Self::is_console(&expr.expression))
|
||||
}
|
||||
|
||||
fn recover_arrow_expression_after_drop_console(&self, expr: &mut ArrowFunctionExpression<'a>) {
|
||||
if self.options.drop_console && expr.expression && expr.body.is_empty() {
|
||||
expr.expression = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_console(expr: &Expression<'_>) -> bool {
|
||||
let Expression::CallExpression(call_expr) = &expr else { return false };
|
||||
let Some(member_expr) = call_expr.callee.as_member_expression() else { return false };
|
||||
let obj = member_expr.object();
|
||||
let Some(ident) = obj.get_identifier_reference() else { return false };
|
||||
ident.name == "console"
|
||||
}
|
||||
|
||||
fn convert_while_to_for(stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
|
|
@ -112,11 +171,16 @@ impl<'a> Normalize {
|
|||
mod test {
|
||||
use oxc_allocator::Allocator;
|
||||
|
||||
use crate::tester;
|
||||
use crate::{tester, CompressOptions};
|
||||
|
||||
fn test(source_text: &str, expected: &str) {
|
||||
let allocator = Allocator::default();
|
||||
let mut pass = super::Normalize::new();
|
||||
let options = CompressOptions {
|
||||
drop_debugger: true,
|
||||
drop_console: true,
|
||||
..CompressOptions::default()
|
||||
};
|
||||
let mut pass = super::Normalize::new(options);
|
||||
tester::test(&allocator, source_text, expected, &mut pass);
|
||||
}
|
||||
|
||||
|
|
@ -131,4 +195,21 @@ mod test {
|
|||
test("var x; void x", "var x; void 0");
|
||||
test("void x", "void x"); // reference error
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens() {
|
||||
test("(((x)))", "x");
|
||||
test("(((a + b))) * c", "(a + b) * c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_console() {
|
||||
test("console.log()", "void 0;\n");
|
||||
test("() => console.log()", "() => void 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_debugger() {
|
||||
test("debugger", "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
|
||||
|
||||
use crate::{CompressOptions, CompressorPass};
|
||||
|
||||
/// Remove syntax from the AST.
|
||||
///
|
||||
/// * Parenthesized Expression
|
||||
/// * `debugger`
|
||||
/// * `console.log`
|
||||
pub struct RemoveSyntax {
|
||||
options: CompressOptions,
|
||||
}
|
||||
|
||||
impl<'a> CompressorPass<'a> for RemoveSyntax {
|
||||
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
|
||||
traverse_mut_with_ctx(self, program, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Traverse<'a> for RemoveSyntax {
|
||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) {
|
||||
stmts.retain(|stmt| {
|
||||
!(matches!(stmt, Statement::EmptyStatement(_))
|
||||
|| self.drop_debugger(stmt)
|
||||
|| self.drop_console(stmt))
|
||||
});
|
||||
}
|
||||
|
||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.compress_console(expr, ctx);
|
||||
Self::strip_parenthesized_expression(expr, ctx);
|
||||
}
|
||||
|
||||
fn exit_arrow_function_expression(
|
||||
&mut self,
|
||||
expr: &mut ArrowFunctionExpression<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
self.recover_arrow_expression_after_drop_console(expr);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSyntax {
|
||||
pub fn new(options: CompressOptions) -> Self {
|
||||
Self { options }
|
||||
}
|
||||
|
||||
fn strip_parenthesized_expression(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if let Expression::ParenthesizedExpression(paren_expr) = expr {
|
||||
*expr = ctx.ast.move_expression(&mut paren_expr.expression);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop `drop_debugger` statement.
|
||||
///
|
||||
/// Enabled by `compress.drop_debugger`
|
||||
fn drop_debugger(&mut self, stmt: &Statement<'a>) -> bool {
|
||||
matches!(stmt, Statement::DebuggerStatement(_)) && self.options.drop_debugger
|
||||
}
|
||||
|
||||
/// Drop `console.*` expressions.
|
||||
///
|
||||
/// Enabled by `compress.drop_console
|
||||
fn drop_console(&mut self, stmt: &Statement<'a>) -> bool {
|
||||
self.options.drop_console
|
||||
&& matches!(stmt, Statement::ExpressionStatement(expr) if Self::is_console(&expr.expression))
|
||||
}
|
||||
|
||||
fn compress_console(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if self.options.drop_console && Self::is_console(expr) {
|
||||
*expr = ctx.ast.void_0(expr.span());
|
||||
}
|
||||
}
|
||||
|
||||
fn recover_arrow_expression_after_drop_console(&self, expr: &mut ArrowFunctionExpression<'a>) {
|
||||
if self.options.drop_console && expr.expression && expr.body.is_empty() {
|
||||
expr.expression = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_console(expr: &Expression<'_>) -> bool {
|
||||
// let Statement::ExpressionStatement(expr) = stmt else { return false };
|
||||
let Expression::CallExpression(call_expr) = &expr else { return false };
|
||||
let Some(member_expr) = call_expr.callee.as_member_expression() else { return false };
|
||||
let obj = member_expr.object();
|
||||
let Some(ident) = obj.get_identifier_reference() else { return false };
|
||||
ident.name == "console"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use oxc_allocator::Allocator;
|
||||
|
||||
use crate::{tester, CompressOptions};
|
||||
|
||||
fn test(source_text: &str, expected: &str) {
|
||||
let allocator = Allocator::default();
|
||||
let mut pass = super::RemoveSyntax::new(CompressOptions::all_true());
|
||||
tester::test(&allocator, source_text, expected, &mut pass);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens() {
|
||||
test("(((x)))", "x");
|
||||
test("(((a + b))) * c", "(a + b) * c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_console() {
|
||||
test("console.log()", "void 0;\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_debugger() {
|
||||
test("debugger", "");
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use oxc_semantic::{ScopeTree, SemanticBuilder, SymbolTable};
|
|||
use oxc_traverse::ReusableTraverseCtx;
|
||||
|
||||
use crate::{
|
||||
ast_passes::{DeadCodeElimination, Normalize, PeepholeOptimizations, RemoveSyntax},
|
||||
ast_passes::{DeadCodeElimination, Normalize, PeepholeOptimizations},
|
||||
CompressOptions, CompressorPass,
|
||||
};
|
||||
|
||||
|
|
@ -31,9 +31,8 @@ impl<'a> Compressor<'a> {
|
|||
program: &mut Program<'a>,
|
||||
) {
|
||||
let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator);
|
||||
RemoveSyntax::new(self.options).build(program, &mut ctx);
|
||||
// RemoveUnusedCode::new(self.options).build(program, &mut ctx);
|
||||
Normalize::new().build(program, &mut ctx);
|
||||
Normalize::new(self.options).build(program, &mut ctx);
|
||||
PeepholeOptimizations::new(self.options.target, true, self.options)
|
||||
.run_in_loop(program, &mut ctx);
|
||||
PeepholeOptimizations::new(self.options.target, false, self.options)
|
||||
|
|
@ -53,7 +52,7 @@ impl<'a> Compressor<'a> {
|
|||
program: &mut Program<'a>,
|
||||
) {
|
||||
let mut ctx = ReusableTraverseCtx::new(scopes, symbols, self.allocator);
|
||||
RemoveSyntax::new(self.options).build(program, &mut ctx);
|
||||
Normalize::new(self.options).build(program, &mut ctx);
|
||||
DeadCodeElimination::new().build(program, &mut ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use oxc_span::SourceType;
|
|||
use oxc_traverse::ReusableTraverseCtx;
|
||||
|
||||
use crate::{
|
||||
ast_passes::{CompressorPass, Normalize, RemoveSyntax},
|
||||
ast_passes::{CompressorPass, Normalize},
|
||||
CompressOptions,
|
||||
};
|
||||
|
||||
|
|
@ -44,8 +44,7 @@ fn run<'a, P: CompressorPass<'a>>(
|
|||
let (symbols, scopes) =
|
||||
SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree();
|
||||
let mut ctx = ReusableTraverseCtx::new(scopes, symbols, allocator);
|
||||
RemoveSyntax::new(CompressOptions::all_false()).build(&mut program, &mut ctx);
|
||||
Normalize::new().build(&mut program, &mut ctx);
|
||||
Normalize::new(CompressOptions::all_false()).build(&mut program, &mut ctx);
|
||||
pass.build(&mut program, &mut ctx);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue