swc_ecma_minifier/
lib.rs

1//! JavaScript minifier implemented in rust.
2//!
3//! # Assumptions
4//!
5//! Like other minification tools, swc minifier assumes some things about the
6//! input code.
7//!
8//!  - TDZ violation does not exist.
9//!
10//! In other words, TDZ violation will be ignored.
11//!
12//!  - Acesssing top-level identifiers do not have side effects.
13//!
14//! If you declare a variable on `globalThis` using a getter with side-effects,
15//! swc minifier will break it.
16//!
17//! # Debugging
18//!
19//! In debug build, if you set an environment variable `SWC_CHECK` to `1`, the
20//! minifier will check the validity of the syntax using `node --check`
21//!
22//! # Cargo features
23//!
24//! ## `debug`
25//!
26//! If you enable this cargo feature and set the environment variable named
27//! `SWC_RUN` to `1`, the minifier will validate the code using node before each
28//! step.
29#![deny(clippy::all)]
30#![allow(clippy::blocks_in_conditions)]
31#![allow(clippy::collapsible_else_if)]
32#![allow(clippy::collapsible_if)]
33#![allow(clippy::ptr_arg)]
34#![allow(clippy::vec_box)]
35#![allow(clippy::overly_complex_bool_expr)]
36#![allow(clippy::mutable_key_type)]
37#![allow(clippy::only_used_in_recursion)]
38#![allow(unstable_name_collisions)]
39#![allow(clippy::match_like_matches_macro)]
40
41use once_cell::sync::Lazy;
42use pass::mangle_names::mangle_names;
43use swc_common::{comments::Comments, pass::Repeated, sync::Lrc, SourceMap, SyntaxContext};
44use swc_ecma_ast::*;
45use swc_ecma_transforms_optimization::debug_assert_valid;
46use swc_ecma_usage_analyzer::marks::Marks;
47use swc_ecma_visit::VisitMutWith;
48use swc_timer::timer;
49
50pub use crate::pass::global_defs::globals_defs;
51use crate::{
52    compress::{compressor, pure_optimizer, PureOptimizerConfig},
53    metadata::info_marker,
54    mode::{Minification, Mode},
55    option::{CompressOptions, ExtraOptions, MinifyOptions},
56    pass::{
57        global_defs, mangle_names::idents_to_preserve, mangle_props::mangle_properties,
58        merge_exports::merge_exports, postcompress::postcompress_optimizer,
59    },
60    // program_data::ModuleInfo,
61    timing::Timings,
62    util::base54::CharFreq,
63};
64
65#[macro_use]
66mod macros;
67mod compress;
68mod debug;
69pub mod eval;
70#[doc(hidden)]
71pub mod js;
72mod metadata;
73mod mode;
74pub mod option;
75mod pass;
76mod program_data;
77mod size_hint;
78pub mod timing;
79mod util;
80
81pub mod marks {
82    pub use swc_ecma_usage_analyzer::marks::Marks;
83}
84
85const DISABLE_BUGGY_PASSES: bool = true;
86
87pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);
88pub(crate) static HEAVY_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 8);
89pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);
90
91pub fn optimize(
92    mut n: Program,
93    _cm: Lrc<SourceMap>,
94    comments: Option<&dyn Comments>,
95    mut timings: Option<&mut Timings>,
96    options: &MinifyOptions,
97    extra: &ExtraOptions,
98) -> Program {
99    let _timer = timer!("minify");
100
101    let mut marks = Marks::new();
102    marks.top_level_ctxt = SyntaxContext::empty().apply_mark(extra.top_level_mark);
103    marks.unresolved_mark = extra.unresolved_mark;
104
105    debug_assert_valid(&n);
106
107    if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
108        let _timer = timer!("inline global defs");
109        // Apply global defs.
110        //
111        // As terser treats `CONFIG['VALUE']` and `CONFIG.VALUE` differently, we don't
112        // have to see if optimized code matches global definition and we can run
113        // this at startup.
114
115        if !defs.is_empty() {
116            let defs = defs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
117            n.visit_mut_with(&mut global_defs::globals_defs(
118                defs,
119                extra.unresolved_mark,
120                extra.top_level_mark,
121            ));
122        }
123    }
124
125    if options.compress.is_some() {
126        n.visit_mut_with(&mut info_marker(
127            options.compress.as_ref(),
128            comments,
129            marks,
130            // extra.unresolved_mark,
131        ));
132        debug_assert_valid(&n);
133    }
134
135    if options.wrap {
136        // TODO: wrap_common_js
137        // toplevel = toplevel.wrap_commonjs(options.wrap);
138    }
139
140    if options.enclose {
141        // TODO: enclose
142        // toplevel = toplevel.wrap_enclose(options.enclose);
143    }
144
145    // Noop.
146    // https://github.com/mishoo/UglifyJS2/issues/2794
147    if options.rename && DISABLE_BUGGY_PASSES {
148        // toplevel.figure_out_scope(options.mangle);
149        // TODO: Pass `options.mangle` to name expander.
150        // n.visit_mut_with(&mut name_expander());
151    }
152
153    if let Some(ref mut t) = timings {
154        t.section("compress");
155    }
156    if let Some(c) = &options.compress {
157        {
158            let _timer = timer!("compress ast");
159
160            perform_dce(&mut n, c, marks);
161
162            n.mutate(&mut compressor(
163                marks,
164                c,
165                options.mangle.as_ref(),
166                &Minification,
167            ));
168
169            perform_dce(&mut n, c, marks);
170        }
171
172        // Again, we don't need to validate ast
173
174        let _timer = timer!("postcompress");
175
176        postcompress_optimizer(&mut n, c);
177
178        n.visit_mut_with(&mut pure_optimizer(
179            c,
180            marks,
181            PureOptimizerConfig {
182                force_str_for_tpl: Minification.force_str_for_tpl(),
183                enable_join_vars: true,
184            },
185        ));
186    }
187
188    if let Some(ref mut _t) = timings {
189        // TODO: store `scope`
190    }
191    if options.mangle.is_some() {
192        // toplevel.figure_out_scope(options.mangle);
193    }
194
195    if let Some(ref mut t) = timings {
196        t.section("mangle");
197    }
198
199    if let Some(mangle) = &options.mangle {
200        let _timer = timer!("mangle names");
201        // TODO: base54.reset();
202
203        let preserved = idents_to_preserve(mangle, marks, &n);
204
205        let chars = if !mangle.disable_char_freq {
206            debug_assert!(preserved.idents.is_some());
207            CharFreq::compute(&n, preserved.idents.as_ref().unwrap()).compile()
208        } else {
209            debug_assert!(preserved.idents.is_none());
210            CharFreq::default().compile()
211        };
212
213        mangle_names(
214            &mut n,
215            mangle,
216            preserved.preserved,
217            chars,
218            extra.top_level_mark,
219            extra.mangle_name_cache.clone(),
220        );
221
222        if let Some(property_mangle_options) = &mangle.props {
223            mangle_properties(&mut n, property_mangle_options, chars);
224        }
225    }
226
227    n.visit_mut_with(&mut merge_exports());
228
229    if let Some(ref mut t) = timings {
230        t.section("hygiene");
231        t.end_section();
232    }
233
234    n
235}
236
237fn perform_dce(m: &mut Program, options: &CompressOptions, extra: Marks) {
238    if !options.unused && !options.dead_code {
239        return;
240    }
241
242    let _timer = timer!("remove dead code");
243
244    let mut visitor = swc_ecma_transforms_optimization::simplify::dce::dce(
245        swc_ecma_transforms_optimization::simplify::dce::Config {
246            module_mark: None,
247            top_level: options.top_level(),
248            top_retain: options.top_retain.clone(),
249            preserve_imports_with_side_effects: true,
250        },
251        extra.unresolved_mark,
252    );
253
254    loop {
255        #[cfg(feature = "debug")]
256        let start = crate::debug::dump(&*m, false);
257
258        m.visit_mut_with(&mut visitor);
259
260        #[cfg(feature = "debug")]
261        if visitor.changed() {
262            let src = crate::debug::dump(&*m, false);
263            tracing::debug!(
264                "===== Before DCE =====\n{}\n===== After DCE =====\n{}",
265                start,
266                src
267            );
268        }
269
270        if !visitor.changed() {
271            break;
272        }
273
274        visitor.reset();
275    }
276
277    debug_assert_valid(&*m);
278}