refactor(minifier): remove all the unnecessary fake ast passes (#8618)

This also removes handling of making cjs-module-lexer to work.
This commit is contained in:
Boshen 2025-01-20 22:05:51 +08:00 committed by GitHub
parent 787aaad977
commit 6f95cd599a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 203 additions and 511 deletions

View file

@ -1,41 +1,27 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::TraverseCtx;
use crate::CompressorPass;
use super::PeepholeOptimizations;
/// Collapse variable declarations.
///
/// Join Vars:
/// `var a; var b = 1; var c = 2` => `var a, b = 1; c = 2`
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/CollapseVariableDeclarations.java>
///
/// Collapse into for statements:
/// `var a = 0; for(;a<0;a++) {}` => `for(var a = 0;a<0;a++) {}`
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/Denormalize.java>
pub struct CollapseVariableDeclarations {
pub(crate) changed: bool,
}
impl<'a> CompressorPass<'a> for CollapseVariableDeclarations {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for CollapseVariableDeclarations {
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
impl<'a> PeepholeOptimizations {
/// Collapse variable declarations.
///
/// Join Vars:
/// `var a; var b = 1; var c = 2` => `var a, b = 1; c = 2`
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/CollapseVariableDeclarations.java>
///
/// Collapse into for statements:
/// `var a = 0; for(;a<0;a++) {}` => `for(var a = 0;a<0;a++) {}`
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/Denormalize.java>
pub fn collapse_variable_declarations(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
self.join_vars(stmts, ctx);
self.maybe_collapse_into_for_statements(stmts, ctx);
}
}
// Join Vars
impl<'a> CollapseVariableDeclarations {
pub fn new() -> Self {
Self { changed: false }
}
fn is_require_call(var_decl: &VariableDeclaration) -> bool {
var_decl
@ -112,7 +98,7 @@ impl<'a> CollapseVariableDeclarations {
}
// Collapse into for statements
impl<'a> CollapseVariableDeclarations {
impl<'a> PeepholeOptimizations {
fn maybe_collapse_into_for_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
@ -240,18 +226,6 @@ mod test {
mod join_vars {
use super::{test, test_same};
#[test]
fn cjs() {
// Do not join `require` calls for cjs-module-lexer.
test_same(
" Object.defineProperty(exports, '__esModule', { value: true });
var compilerDom = require('@vue/compiler-dom');
var runtimeDom = require('@vue/runtime-dom');
var shared = require('@vue/shared');
",
);
}
#[test]
fn test_collapsing() {
// Basic collapsing

View file

@ -1,27 +1,16 @@
use oxc_ast::ast::*;
use oxc_syntax::identifier::is_identifier_name;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::TraverseCtx;
use crate::{ctx::Ctx, CompressorPass};
use super::PeepholeOptimizations;
use crate::ctx::Ctx;
/// Converts property accesses from quoted string or bracket access syntax to dot or unquoted string
/// syntax, where possible. Dot syntax is more compact.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/ConvertToDottedProperties.java>
pub struct ConvertToDottedProperties {
pub(crate) changed: bool,
in_fixed_loop: bool,
}
impl<'a> CompressorPass<'a> for ConvertToDottedProperties {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = true;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for ConvertToDottedProperties {
fn exit_member_expression(
impl<'a> PeepholeOptimizations {
/// Converts property accesses from quoted string or bracket access syntax to dot or unquoted string
/// syntax, where possible. Dot syntax is more compact.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/ConvertToDottedProperties.java>
pub fn convert_to_dotted_properties(
&mut self,
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
@ -30,12 +19,6 @@ impl<'a> Traverse<'a> for ConvertToDottedProperties {
self.try_compress_computed_member_expression(expr, Ctx(ctx));
}
}
}
impl<'a> ConvertToDottedProperties {
pub fn new(in_fixed_loop: bool) -> Self {
Self { changed: false, in_fixed_loop }
}
/// `foo['bar']` -> `foo.bar`
/// `foo?.['bar']` -> `foo?.bar`

View file

@ -1,31 +1,19 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::TraverseCtx;
use crate::CompressorPass;
use super::PeepholeOptimizations;
/// Tries to chain assignments together.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/ExploitAssigns.java>
pub struct ExploitAssigns {
pub(crate) changed: bool,
}
impl<'a> CompressorPass<'a> for ExploitAssigns {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for ExploitAssigns {
fn exit_statements(&mut self, _stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) {
}
}
impl ExploitAssigns {
pub fn new() -> Self {
Self { changed: false }
impl<'a> PeepholeOptimizations {
/// Tries to chain assignments together.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/ExploitAssigns.java>
#[expect(clippy::unused_self)]
pub fn exploit_assigns(
&mut self,
_stmts: &mut Vec<'a, Statement<'a>>,
_ctx: &mut TraverseCtx<'a>,
) {
}
}

View file

@ -1,34 +1,21 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::TraverseCtx;
use crate::CompressorPass;
use super::PeepholeOptimizations;
/// Transform the structure of the AST so that the number of explicit exits
/// are minimized and instead flows to implicit exits conditions.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/MinimizeExitPoints.java>
pub struct MinimizeExitPoints {
pub(crate) changed: bool,
}
impl<'a> CompressorPass<'a> for MinimizeExitPoints {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl Traverse<'_> for MinimizeExitPoints {
fn exit_function_body(&mut self, body: &mut FunctionBody<'_>, _ctx: &mut TraverseCtx<'_>) {
impl<'a> PeepholeOptimizations {
/// Transform the structure of the AST so that the number of explicit exits
/// are minimized and instead flows to implicit exits conditions.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/MinimizeExitPoints.java>
pub fn minimize_exit_points(
&mut self,
body: &mut FunctionBody<'_>,
_ctx: &mut TraverseCtx<'_>,
) {
self.remove_last_return(&mut body.statements);
}
}
impl<'a> MinimizeExitPoints {
pub fn new() -> Self {
Self { changed: false }
}
// `function foo() { return }` -> `function foo() {}`
fn remove_last_return(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {

View file

@ -16,84 +16,25 @@ mod peephole_substitute_alternate_syntax;
mod remove_unused_code;
mod statement_fusion;
pub use collapse_variable_declarations::CollapseVariableDeclarations;
pub use convert_to_dotted_properties::ConvertToDottedProperties;
pub use exploit_assigns::ExploitAssigns;
pub use minimize_exit_points::MinimizeExitPoints;
pub use normalize::{Normalize, NormalizeOptions};
pub use peephole_fold_constants::PeepholeFoldConstants;
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;
#[expect(unused)]
pub use remove_unused_code::RemoveUnusedCode;
pub use statement_fusion::StatementFusion;
use crate::CompressOptions;
pub trait CompressorPass<'a>: Traverse<'a> {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>);
}
pub struct PeepholeOptimizations {
x0_statement_fusion: StatementFusion,
x1_minimize_exit_points: MinimizeExitPoints,
x2_exploit_assigns: ExploitAssigns,
x3_collapse_variable_declarations: CollapseVariableDeclarations,
x4_peephole_fold_constants: PeepholeFoldConstants,
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode,
x5_peephole_minimize_conditions: PeepholeMinimizeConditions,
x7_convert_to_dotted_properties: ConvertToDottedProperties,
x8_peephole_replace_known_methods: PeepholeReplaceKnownMethods,
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax,
target: ESTarget,
changed: bool,
/// `in_fixed_loop`: Do not compress syntaxes that are hard to analyze inside the fixed loop.
/// Opposite of `late` in Closure Compiler.
in_fixed_loop: bool,
}
impl PeepholeOptimizations {
/// `in_fixed_loop`: Do not compress syntaxes that are hard to analyze inside the fixed loop.
/// Opposite of `late` in Closure Compiler.
pub fn new(target: ESTarget, in_fixed_loop: bool, options: CompressOptions) -> Self {
Self {
x0_statement_fusion: StatementFusion::new(),
x1_minimize_exit_points: MinimizeExitPoints::new(),
x2_exploit_assigns: ExploitAssigns::new(),
x3_collapse_variable_declarations: CollapseVariableDeclarations::new(),
x4_peephole_fold_constants: PeepholeFoldConstants::new(),
x5_peephole_minimize_conditions: PeepholeMinimizeConditions::new(target),
x6_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(in_fixed_loop),
x7_convert_to_dotted_properties: ConvertToDottedProperties::new(in_fixed_loop),
x8_peephole_replace_known_methods: PeepholeReplaceKnownMethods::new(),
x9_peephole_substitute_alternate_syntax: PeepholeSubstituteAlternateSyntax::new(
options.target,
in_fixed_loop,
),
}
}
fn reset_changed(&mut self) {
self.x0_statement_fusion.changed = false;
self.x1_minimize_exit_points.changed = false;
self.x2_exploit_assigns.changed = false;
self.x3_collapse_variable_declarations.changed = false;
self.x4_peephole_fold_constants.changed = false;
self.x5_peephole_minimize_conditions.changed = false;
self.x6_peephole_remove_dead_code.changed = false;
self.x7_convert_to_dotted_properties.changed = false;
self.x8_peephole_replace_known_methods.changed = false;
self.x9_peephole_substitute_alternate_syntax.changed = false;
}
fn changed(&self) -> bool {
self.x0_statement_fusion.changed
|| self.x1_minimize_exit_points.changed
|| self.x2_exploit_assigns.changed
|| self.x3_collapse_variable_declarations.changed
|| self.x4_peephole_fold_constants.changed
|| self.x5_peephole_minimize_conditions.changed
|| self.x6_peephole_remove_dead_code.changed
|| self.x7_convert_to_dotted_properties.changed
|| self.x8_peephole_replace_known_methods.changed
|| self.x9_peephole_substitute_alternate_syntax.changed
pub fn new(target: ESTarget, in_fixed_loop: bool) -> Self {
Self { target, changed: false, in_fixed_loop }
}
pub fn run_in_loop<'a>(
@ -103,9 +44,9 @@ impl PeepholeOptimizations {
) {
let mut i = 0;
loop {
self.reset_changed();
self.changed = false;
self.build(program, ctx);
if !self.changed() {
if !self.changed {
break;
}
if i > 10 {
@ -124,33 +65,29 @@ impl<'a> CompressorPass<'a> for PeepholeOptimizations {
}
impl<'a> Traverse<'a> for PeepholeOptimizations {
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
self.x6_peephole_remove_dead_code.exit_program(program, ctx);
}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_statement_fusion.exit_statements(stmts, ctx);
self.x2_exploit_assigns.exit_statements(stmts, ctx);
self.x3_collapse_variable_declarations.exit_statements(stmts, ctx);
self.x5_peephole_minimize_conditions.exit_statements(stmts, ctx);
self.x6_peephole_remove_dead_code.exit_statements(stmts, ctx);
self.statement_fusion_exit_statements(stmts, ctx);
self.exploit_assigns(stmts, ctx);
self.collapse_variable_declarations(stmts, ctx);
self.minimize_conditions_exit_statements(stmts, ctx);
self.remove_dead_code_exit_statements(stmts, ctx);
}
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
self.x5_peephole_minimize_conditions.exit_statement(stmt, ctx);
self.x6_peephole_remove_dead_code.exit_statement(stmt, ctx);
self.minimize_conditions_exit_statement(stmt, ctx);
self.remove_dead_code_exit_statement(stmt, ctx);
}
fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_peephole_substitute_alternate_syntax.exit_return_statement(stmt, ctx);
self.substitute_return_statement(stmt, ctx);
}
fn exit_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
self.x1_minimize_exit_points.exit_function_body(body, ctx);
self.minimize_exit_points(body, ctx);
}
fn exit_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
self.x6_peephole_remove_dead_code.exit_class_body(body, ctx);
self.remove_dead_code_exit_class_body(body, ctx);
}
fn exit_variable_declaration(
@ -158,23 +95,19 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
decl: &mut VariableDeclaration<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x9_peephole_substitute_alternate_syntax.exit_variable_declaration(decl, ctx);
self.substitute_variable_declaration(decl, ctx);
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x4_peephole_fold_constants.exit_expression(expr, ctx);
self.x5_peephole_minimize_conditions.exit_expression(expr, ctx);
self.x6_peephole_remove_dead_code.exit_expression(expr, ctx);
self.x8_peephole_replace_known_methods.exit_expression(expr, ctx);
self.x9_peephole_substitute_alternate_syntax.exit_expression(expr, ctx);
}
fn enter_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_peephole_substitute_alternate_syntax.enter_call_expression(expr, ctx);
self.fold_constants_exit_expression(expr, ctx);
self.minimize_conditions_exit_expression(expr, ctx);
self.remove_dead_code_exit_expression(expr, ctx);
self.replace_known_methods_exit_expression(expr, ctx);
self.substitute_exit_expression(expr, ctx);
}
fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx);
self.substitute_call_expression(expr, ctx);
}
fn exit_member_expression(
@ -182,11 +115,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x7_convert_to_dotted_properties.exit_member_expression(expr, ctx);
self.convert_to_dotted_properties(expr, ctx);
}
fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_peephole_substitute_alternate_syntax.exit_object_property(prop, ctx);
self.substitute_object_property(prop, ctx);
}
fn exit_assignment_target_property_property(
@ -194,12 +127,11 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
prop: &mut AssignmentTargetPropertyProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x9_peephole_substitute_alternate_syntax
.exit_assignment_target_property_property(prop, ctx);
self.substitute_assignment_target_property_property(prop, ctx);
}
fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_peephole_substitute_alternate_syntax.exit_binding_property(prop, ctx);
self.substitute_binding_property(prop, ctx);
}
fn exit_method_definition(
@ -207,7 +139,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
prop: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x9_peephole_substitute_alternate_syntax.exit_method_definition(prop, ctx);
self.substitute_method_definition(prop, ctx);
}
fn exit_property_definition(
@ -215,7 +147,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
prop: &mut PropertyDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x9_peephole_substitute_alternate_syntax.exit_property_definition(prop, ctx);
self.substitute_property_definition(prop, ctx);
}
fn exit_accessor_property(
@ -223,25 +155,21 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
prop: &mut AccessorProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x9_peephole_substitute_alternate_syntax.exit_accessor_property(prop, ctx);
self.substitute_accessor_property(prop, ctx);
}
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_peephole_substitute_alternate_syntax.exit_catch_clause(catch, ctx);
self.substitute_catch_clause(catch, ctx);
}
}
pub struct DeadCodeElimination {
x1_peephole_fold_constants: PeepholeFoldConstants,
x2_peephole_remove_dead_code: PeepholeRemoveDeadCode,
inner: PeepholeOptimizations,
}
impl DeadCodeElimination {
pub fn new() -> Self {
Self {
x1_peephole_fold_constants: PeepholeFoldConstants::new(),
x2_peephole_remove_dead_code: PeepholeRemoveDeadCode::new(false),
}
Self { inner: PeepholeOptimizations::new(ESTarget::ESNext, false) }
}
}
@ -253,19 +181,15 @@ impl<'a> CompressorPass<'a> for DeadCodeElimination {
impl<'a> Traverse<'a> for DeadCodeElimination {
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
self.x2_peephole_remove_dead_code.exit_statement(stmt, ctx);
}
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
self.x2_peephole_remove_dead_code.exit_program(program, ctx);
self.inner.remove_dead_code_exit_statement(stmt, ctx);
}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x2_peephole_remove_dead_code.exit_statements(stmts, ctx);
self.inner.remove_dead_code_exit_statements(stmts, ctx);
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x1_peephole_fold_constants.exit_expression(expr, ctx);
self.x2_peephole_remove_dead_code.exit_expression(expr, ctx);
self.inner.fold_constants_exit_expression(expr, ctx);
self.inner.remove_dead_code_exit_expression(expr, ctx);
}
}

View file

@ -8,26 +8,21 @@ use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, LogicalOperator},
};
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{Ancestor, TraverseCtx};
use crate::{ctx::Ctx, CompressorPass};
use crate::ctx::Ctx;
/// Constant Folding
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
pub struct PeepholeFoldConstants {
pub(crate) changed: bool,
}
use super::PeepholeOptimizations;
impl<'a> CompressorPass<'a> for PeepholeFoldConstants {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for PeepholeFoldConstants {
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
impl<'a, 'b> PeepholeOptimizations {
/// Constant Folding
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
pub fn fold_constants_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
if let Some(folded_expr) = match expr {
Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx)
@ -44,12 +39,6 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants {
self.changed = true;
};
}
}
impl<'a, 'b> PeepholeFoldConstants {
pub fn new() -> Self {
Self { changed: false }
}
#[expect(clippy::float_cmp)]
fn try_fold_unary_expr(e: &UnaryExpression<'a>, ctx: Ctx<'a, 'b>) -> Option<Expression<'a>> {

View file

@ -2,10 +2,11 @@ use oxc_allocator::Vec;
use oxc_ast::{ast::*, NONE};
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, ValueType};
use oxc_span::{cmp::ContentEq, GetSpan};
use oxc_syntax::es_target::ESTarget;
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{Ancestor, TraverseCtx};
use crate::{ctx::Ctx, CompressorPass};
use crate::ctx::Ctx;
use super::PeepholeOptimizations;
/// Minimize Conditions
///
@ -14,21 +15,8 @@ use crate::{ctx::Ctx, CompressorPass};
/// with `? :` and short-circuit binary operators.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeMinimizeConditions.java>
pub struct PeepholeMinimizeConditions {
#[allow(unused)]
target: ESTarget,
pub(crate) changed: bool,
}
impl<'a> CompressorPass<'a> for PeepholeMinimizeConditions {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
fn exit_statements(
impl<'a> PeepholeOptimizations {
pub fn minimize_conditions_exit_statements(
&mut self,
stmts: &mut oxc_allocator::Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
@ -45,7 +33,11 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
self.changed = self.changed || changed;
}
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn minimize_conditions_exit_statement(
&mut self,
stmt: &mut Statement<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let expr = match stmt {
Statement::IfStatement(s) => Some(&mut s.test),
Statement::WhileStatement(s) => Some(&mut s.test),
@ -76,7 +68,11 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
};
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn minimize_conditions_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
loop {
let mut changed = false;
if let Expression::ConditionalExpression(logical_expr) = expr {
@ -106,12 +102,6 @@ impl<'a> Traverse<'a> for PeepholeMinimizeConditions {
self.changed = true;
};
}
}
impl<'a> PeepholeMinimizeConditions {
pub fn new(target: ESTarget) -> Self {
Self { target, changed: false }
}
fn try_minimize_not(
expr: &mut UnaryExpression<'a>,

View file

@ -5,9 +5,11 @@ use oxc_ecmascript::{
side_effects::MayHaveSideEffects,
};
use oxc_span::GetSpan;
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{Ancestor, TraverseCtx};
use crate::{ctx::Ctx, keep_var::KeepVar, CompressorPass};
use crate::{ctx::Ctx, keep_var::KeepVar};
use super::PeepholeOptimizations;
/// Remove Dead Code from the AST.
///
@ -15,28 +17,23 @@ use crate::{ctx::Ctx, keep_var::KeepVar, CompressorPass};
///
/// See `KeepVar` at the end of this file for `var` hoisting logic.
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeRemoveDeadCode.java>
pub struct PeepholeRemoveDeadCode {
pub(crate) changed: bool,
in_fixed_loop: bool,
}
impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
impl<'a, 'b> PeepholeOptimizations {
pub fn remove_dead_code_exit_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) {
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
}
self.dead_code_elimination(stmts, Ctx(ctx));
}
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn remove_dead_code_exit_statement(
&mut self,
stmt: &mut Statement<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
if let Some(new_stmt) = match stmt {
Statement::BlockStatement(s) => Self::try_optimize_block(s, ctx),
@ -59,7 +56,11 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn remove_dead_code_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
if let Some(folded_expr) = match expr {
Expression::ConditionalExpression(e) => Self::try_fold_conditional_expression(e, ctx),
@ -73,17 +74,15 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
}
}
fn exit_class_body(&mut self, body: &mut ClassBody<'a>, _ctx: &mut TraverseCtx<'a>) {
pub fn remove_dead_code_exit_class_body(
&mut self,
body: &mut ClassBody<'a>,
_ctx: &mut TraverseCtx<'a>,
) {
if !self.in_fixed_loop {
Self::remove_empty_class_static_block(body);
}
}
}
impl<'a, 'b> PeepholeRemoveDeadCode {
pub fn new(in_fixed_loop: bool) -> Self {
Self { changed: false, in_fixed_loop }
}
/// Removes dead code thats comes after `return` statements after inlining `if` statements
fn dead_code_elimination(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: Ctx<'a, 'b>) {

View file

@ -7,34 +7,23 @@ use oxc_ecmascript::{
constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf,
StringLastIndexOf, StringSubstring, ToInt32,
};
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{Ancestor, TraverseCtx};
use crate::{ctx::Ctx, CompressorPass};
use crate::ctx::Ctx;
/// Minimize With Known Methods
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeReplaceKnownMethods.java>
pub struct PeepholeReplaceKnownMethods {
pub(crate) changed: bool,
}
use super::PeepholeOptimizations;
impl<'a> CompressorPass<'a> for PeepholeReplaceKnownMethods {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for PeepholeReplaceKnownMethods {
fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
impl<'a> PeepholeOptimizations {
/// Minimize With Known Methods
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeReplaceKnownMethods.java>
pub fn replace_known_methods_exit_expression(
&mut self,
node: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_fold_concat_chain(node, ctx);
self.try_fold_known_string_methods(node, ctx);
}
}
impl<'a> PeepholeReplaceKnownMethods {
pub fn new() -> Self {
Self { changed: false }
}
fn try_fold_known_string_methods(
&mut self,

View file

@ -13,41 +13,34 @@ use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, UnaryOperator},
};
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{Ancestor, TraverseCtx};
use crate::{ctx::Ctx, CompressorPass};
use crate::ctx::Ctx;
use super::PeepholeOptimizations;
/// A peephole optimization that minimizes code by simplifying conditional
/// expressions, replacing IFs with HOOKs, replacing object constructors
/// with literals, and simplifying returns.
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntax.java>
pub struct PeepholeSubstituteAlternateSyntax {
target: ESTarget,
in_fixed_loop: bool,
in_define_export: bool,
pub(crate) changed: bool,
}
impl<'a> CompressorPass<'a> for PeepholeSubstituteAlternateSyntax {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
impl<'a, 'b> PeepholeOptimizations {
pub fn substitute_catch_clause(
&mut self,
catch: &mut CatchClause<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.compress_catch_clause(catch, ctx);
}
fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn substitute_object_property(
&mut self,
prop: &mut ObjectProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
fn exit_assignment_target_property_property(
pub fn substitute_assignment_target_property_property(
&mut self,
prop: &mut AssignmentTargetPropertyProperty<'a>,
ctx: &mut TraverseCtx<'a>,
@ -55,11 +48,15 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
self.try_compress_property_key(&mut prop.name, &mut prop.computed, ctx);
}
fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn substitute_binding_property(
&mut self,
prop: &mut BindingProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
fn exit_method_definition(
pub fn substitute_method_definition(
&mut self,
prop: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
@ -67,7 +64,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
fn exit_property_definition(
pub fn substitute_property_definition(
&mut self,
prop: &mut PropertyDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
@ -75,7 +72,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
fn exit_accessor_property(
pub fn substitute_accessor_property(
&mut self,
prop: &mut AccessorProperty<'a>,
ctx: &mut TraverseCtx<'a>,
@ -83,11 +80,15 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}
fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn substitute_return_statement(
&mut self,
stmt: &mut ReturnStatement<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.compress_return_statement(stmt, ctx);
}
fn exit_variable_declaration(
pub fn substitute_variable_declaration(
&mut self,
decl: &mut VariableDeclaration<'a>,
ctx: &mut TraverseCtx<'a>,
@ -97,36 +98,19 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
}
}
/// Set `in_define_export` flag if this is a top-level statement of form:
/// ```js
/// Object.defineProperty(exports, 'Foo', {
/// enumerable: true,
/// get: function() { return Foo_1.Foo; }
/// });
/// ```
fn enter_call_expression(
pub fn substitute_call_expression(
&mut self,
call_expr: &mut CallExpression<'a>,
expr: &mut CallExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if !self.in_fixed_loop {
let parent = ctx.parent();
if (parent.is_expression_statement() || parent.is_sequence_expression())
&& Self::is_object_define_property_exports(call_expr)
{
self.in_define_export = true;
}
}
}
fn exit_call_expression(&mut self, expr: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) {
if !self.in_fixed_loop {
self.in_define_export = false;
}
self.try_compress_call_expression_arguments(expr, ctx);
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
pub fn substitute_exit_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let ctx = Ctx(ctx);
// Change syntax
@ -188,12 +172,6 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
self.changed = true;
}
}
}
impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
pub fn new(target: ESTarget, in_fixed_loop: bool) -> Self {
Self { target, in_fixed_loop, in_define_export: false, changed: false }
}
fn compress_catch_clause(&mut self, catch: &mut CatchClause<'_>, ctx: &mut TraverseCtx<'a>) {
if !self.in_fixed_loop && self.target >= ESTarget::ES2019 {
@ -218,25 +196,6 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
}
}
/// Test `Object.defineProperty(exports, ...)`
fn is_object_define_property_exports(call_expr: &CallExpression<'a>) -> bool {
let Some(Argument::Identifier(ident)) = call_expr.arguments.first() else { return false };
if ident.name != "exports" {
return false;
}
// Use tighter check than `call_expr.callee.is_specific_member_access("Object", "defineProperty")`
// because we're looking for `Object.defineProperty` specifically, not e.g. `Object['defineProperty']`
if let Expression::StaticMemberExpression(callee) = &call_expr.callee {
if let Expression::Identifier(id) = &callee.object {
if id.name == "Object" && callee.property.name == "defineProperty" {
return true;
}
}
}
false
}
/// Transforms `undefined` => `void 0`
fn try_compress_undefined(
&self,
@ -265,9 +224,6 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
) -> Option<Expression<'a>> {
debug_assert!(!self.in_fixed_loop);
let Expression::BooleanLiteral(lit) = expr else { return None };
if self.in_define_export {
return None;
}
let parent = ctx.ancestry.parent();
let no_unary = {
if let Ancestor::BinaryExpressionRight(u) = parent {

View file

@ -2,36 +2,23 @@ use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_ecmascript::side_effects::MayHaveSideEffects;
use oxc_span::GetSpan;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::TraverseCtx;
use crate::CompressorPass;
use super::PeepholeOptimizations;
/// Statement Fusion
///
/// Tries to fuse all the statements in a block into a one statement by using COMMAs or statements.
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/StatementFusion.java>
pub struct StatementFusion {
pub(crate) changed: bool,
}
impl<'a> CompressorPass<'a> for StatementFusion {
fn build(&mut self, program: &mut Program<'a>, ctx: &mut ReusableTraverseCtx<'a>) {
self.changed = false;
traverse_mut_with_ctx(self, program, ctx);
}
}
impl<'a> Traverse<'a> for StatementFusion {
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
impl<'a> PeepholeOptimizations {
pub fn statement_fusion_exit_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
self.fuse_statements(stmts, ctx);
}
}
impl<'a> StatementFusion {
pub fn new() -> Self {
Self { changed: false }
}
fn fuse_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
let len = stmts.len();

View file

@ -34,10 +34,8 @@ impl<'a> Compressor<'a> {
// RemoveUnusedCode::new(self.options).build(program, &mut ctx);
let normalize_options = NormalizeOptions { convert_while_to_fors: true };
Normalize::new(normalize_options, 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)
.build(program, &mut ctx);
PeepholeOptimizations::new(self.options.target, true).run_in_loop(program, &mut ctx);
PeepholeOptimizations::new(self.options.target, false).build(program, &mut ctx);
}
pub fn dead_code_elimination(self, program: &mut Program<'a>) {

View file

@ -98,75 +98,3 @@ fn tagged_template() {
test("foo(true && o.f)", "foo(o.f)");
test("foo(true ? o.f : false)", "foo(o.f)");
}
#[test]
fn cjs() {
// Bail `cjs-module-lexer`.
test_same("0 && (module.exports = { version });");
// Bail `cjs-module-lexer`.
// Export is undefined when `enumerable` is "!0".
// https://github.com/nodejs/cjs-module-lexer/issues/64
test_same(
"Object.defineProperty(exports, 'ConnectableObservable', {
enumerable: true,
get: function() {
return ConnectableObservable_1.ConnectableObservable;
}
});",
);
test_same(
"Object.defineProperty(exports, 'ConnectableObservable', {
enumerable: true,
get: function() {
return ConnectableObservable_1.ConnectableObservable;
}
});",
);
// @babel/types/lib/index.js
test(
r#"
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "TargetNames", {
enumerable: true,
get: function () {
return _options.TargetNames;
}
});"#,
r#"
Object.defineProperty(exports, "__esModule", { value: true }), Object.defineProperty(exports, "TargetNames", {
enumerable: true,
get: function() {
return _options.TargetNames;
}
});"#,
);
test(
r#"Object.keys(_index6).forEach(function(key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _index6[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function() {
return _index6[key];
}
});
});"#,
"
Object.keys(_index6).forEach(function(key) {
if (key === 'default' || key === '__esModule') return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _index6[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function() {
return _index6[key];
}
});
});",
);
}

View file

@ -19,7 +19,7 @@ Original | minified | minified | gzip | gzip | Fixture
2.14 MB | 724.06 kB | 724.14 kB | 179.94 kB | 181.07 kB | victory.js
3.20 MB | 1.01 MB | 1.01 MB | 332.01 kB | 331.56 kB | echarts.js
3.20 MB | 1.01 MB | 1.01 MB | 332.00 kB | 331.56 kB | echarts.js
6.69 MB | 2.31 MB | 2.31 MB | 492.53 kB | 488.28 kB | antd.js