Skip to main content

oxc_minifier/
compressor.rs

1use oxc_allocator::Allocator;
2use oxc_ast::ast::*;
3use oxc_semantic::{Scoping, SemanticBuilder};
4use oxc_traverse::ReusableTraverseCtx;
5
6use crate::{
7    CompressOptions,
8    peephole::{Normalize, NormalizeOptions, PeepholeOptimizations},
9    state::MinifierState,
10};
11
12pub struct Compressor<'a> {
13    allocator: &'a Allocator,
14}
15
16impl<'a> Compressor<'a> {
17    pub fn new(allocator: &'a Allocator) -> Self {
18        Self { allocator }
19    }
20
21    pub fn build(self, program: &mut Program<'a>, options: CompressOptions) {
22        let scoping = SemanticBuilder::new().build(program).semantic.into_scoping();
23        self.build_with_scoping(program, scoping, options);
24    }
25
26    /// Returns total number of iterations ran.
27    pub fn build_with_scoping(
28        self,
29        program: &mut Program<'a>,
30        scoping: Scoping,
31        options: CompressOptions,
32    ) -> u8 {
33        let max_iterations = options.max_iterations;
34        let state = MinifierState::new(program.source_type, options, /* dce */ false);
35        let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator);
36        let normalize_options = NormalizeOptions {
37            convert_while_to_fors: true,
38            convert_const_to_let: true,
39            remove_unnecessary_use_strict: true,
40        };
41        Normalize::new(normalize_options).build(program, &mut ctx);
42        Self::run_in_loop(max_iterations, program, &mut ctx)
43    }
44
45    pub fn dead_code_elimination(self, program: &mut Program<'a>, options: CompressOptions) -> u8 {
46        let scoping = SemanticBuilder::new().build(program).semantic.into_scoping();
47        self.dead_code_elimination_with_scoping(program, scoping, options)
48    }
49
50    /// Returns total number of iterations ran.
51    pub fn dead_code_elimination_with_scoping(
52        self,
53        program: &mut Program<'a>,
54        scoping: Scoping,
55        options: CompressOptions,
56    ) -> u8 {
57        let max_iterations = options.max_iterations;
58        let state = MinifierState::new(program.source_type, options, /* dce */ true);
59        let mut ctx = ReusableTraverseCtx::new(state, scoping, self.allocator);
60        let normalize_options = NormalizeOptions {
61            convert_while_to_fors: false,
62            convert_const_to_let: false,
63            remove_unnecessary_use_strict: false,
64        };
65        Normalize::new(normalize_options).build(program, &mut ctx);
66        Self::run_in_loop(max_iterations, program, &mut ctx)
67    }
68
69    /// Fixed-point iteration loop for peephole optimizations.
70    fn run_in_loop(
71        max_iterations: Option<u8>,
72        program: &mut Program<'a>,
73        ctx: &mut ReusableTraverseCtx<'a, MinifierState<'a>>,
74    ) -> u8 {
75        let mut iteration = 0u8;
76        loop {
77            PeepholeOptimizations.run_once(program, ctx);
78            if !ctx.state().changed {
79                break;
80            }
81            if let Some(max) = max_iterations {
82                if iteration >= max {
83                    break;
84                }
85            } else if iteration > 10 {
86                debug_assert!(false, "Ran loop more than 10 times.");
87                break;
88            }
89            iteration += 1;
90        }
91        iteration
92    }
93}