Skip to main content

oxc_minifier/
lib.rs

1//! # Oxc Minifier
2//!
3//! High-performance JavaScript/TypeScript minifier focused on maximum compression.
4//!
5//! ## Overview
6//!
7//! The Oxc minifier applies a comprehensive set of optimizations to JavaScript code
8//! to achieve the smallest possible output while maintaining correctness. It draws
9//! inspiration from industry-leading minifiers like Closure Compiler, Terser, esbuild,
10//! and SWC.
11//!
12//! ## Features
13//!
14//! - **Maximum Compression**: Fixed-point iteration ensures all optimization opportunities are found
15//! - **Comprehensive Optimizations**: 17+ transformation passes and growing
16//! - **100% Correct**: Extensive testing with test262, Babel, and TypeScript test suites
17//! - **Fast**: Efficient algorithms and arena allocation for performance
18//!
19//! ## Example
20//!
21//! ```rust
22//! use oxc_minifier::{Minifier, MinifierOptions};
23//! use oxc_allocator::Allocator;
24//! use oxc_parser::Parser;
25//! use oxc_span::SourceType;
26//!
27//! let allocator = Allocator::default();
28//! let source_text = "const x = 1 + 1; console.log(x);";
29//! let source_type = SourceType::mjs();
30//! let ret = Parser::new(&allocator, source_text, source_type).parse();
31//! let mut program = ret.program;
32//!
33//! let options = MinifierOptions::default();
34//! let minifier = Minifier::new(options);
35//! let result = minifier.minify(&allocator, &mut program);
36//! ```
37//!
38//! ## Architecture
39//!
40//! The minifier consists of:
41//! - **Compressor**: Orchestrates the optimization pipeline
42//! - **Peephole Optimizations**: Individual transformation passes
43//! - **Mangler**: Variable renaming for size reduction
44//!
45//! See the [crate documentation](https://github.com/oxc-project/oxc/tree/main/crates/oxc_minifier) for more details.
46
47mod compressor;
48pub(crate) mod generated;
49mod keep_var;
50mod minifier_traverse;
51mod options;
52mod peephole;
53mod state;
54mod symbol_value;
55mod traverse_context;
56
57use oxc_allocator::Allocator;
58use oxc_ast::ast::Program;
59use oxc_index::IndexVec;
60use oxc_mangler::Mangler;
61use oxc_semantic::{Scoping, SemanticBuilder};
62use oxc_str::CompactStr;
63use oxc_syntax::class::ClassId;
64use rustc_hash::FxHashMap;
65
66pub use oxc_mangler::{MangleOptions, MangleOptionsKeepNames};
67
68pub(crate) use crate::generated::traverse::Traverse;
69#[doc(hidden)]
70pub(crate) use crate::traverse_context::MinifierTraverseCtx as TraverseCtx;
71pub(crate) use crate::traverse_context::ReusableMinifierTraverseCtx as ReusableTraverseCtx;
72pub use crate::{compressor::Compressor, options::*};
73
74#[derive(Debug, Clone)]
75pub struct MinifierOptions {
76    pub mangle: Option<MangleOptions>,
77    pub compress: Option<CompressOptions>,
78}
79
80impl Default for MinifierOptions {
81    fn default() -> Self {
82        Self { mangle: Some(MangleOptions::default()), compress: Some(CompressOptions::default()) }
83    }
84}
85
86pub struct MinifierReturn {
87    pub scoping: Option<Scoping>,
88
89    /// A vector where each element corresponds to a class in declaration order.
90    /// Each element is a mapping from original private member names to their mangled names.
91    pub class_private_mappings: Option<IndexVec<ClassId, FxHashMap<String, CompactStr>>>,
92
93    /// Total number of iterations ran. Useful for debugging performance issues.
94    pub iterations: u8,
95}
96
97pub struct Minifier {
98    options: MinifierOptions,
99}
100
101impl<'a> Minifier {
102    pub fn new(options: MinifierOptions) -> Self {
103        Self { options }
104    }
105
106    pub fn minify(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
107        self.build(false, allocator, program)
108    }
109
110    pub fn dce(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
111        self.build(true, allocator, program)
112    }
113
114    fn build(
115        self,
116        dce: bool,
117        allocator: &'a Allocator,
118        program: &mut Program<'a>,
119    ) -> MinifierReturn {
120        let (stats, iterations) = self
121            .options
122            .compress
123            .map(|options| {
124                let semantic = SemanticBuilder::new().build(program).semantic;
125                let stats = semantic.stats();
126                let scoping = semantic.into_scoping();
127                let compressor = Compressor::new(allocator);
128                let iterations = if dce {
129                    let options = CompressOptions {
130                        target: options.target,
131                        treeshake: options.treeshake,
132                        ..CompressOptions::dce()
133                    };
134                    compressor.dead_code_elimination_with_scoping(program, scoping, options)
135                } else {
136                    compressor.build_with_scoping(program, scoping, options)
137                };
138                (stats, iterations)
139            })
140            .unwrap_or_default();
141        let (scoping, class_private_mappings) = self
142            .options
143            .mangle
144            .map(|options| {
145                let mut semantic = SemanticBuilder::new().with_stats(stats).build(program).semantic;
146                let class_private_mappings = Mangler::default()
147                    .with_options(options)
148                    .build_with_semantic(&mut semantic, program);
149                (semantic.into_scoping(), class_private_mappings)
150            })
151            .map_or((None, None), |(scoping, mappings)| (Some(scoping), Some(mappings)));
152        MinifierReturn { scoping, class_private_mappings, iterations }
153    }
154}