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_utils::ExprCtx;
48use swc_ecma_visit::VisitMutWith;
49use swc_timer::timer;
50
51pub use crate::pass::global_defs::globals_defs;
52use crate::{
53    compress::{compressor, pure_optimizer, PureOptimizerConfig},
54    metadata::info_marker,
55    mode::{Minification, Mode},
56    option::{CompressOptions, ExtraOptions, MinifyOptions},
57    pass::{
58        global_defs, mangle_names::idents_to_preserve, mangle_props::mangle_properties,
59        merge_exports::merge_exports, postcompress::postcompress_optimizer,
60        precompress::precompress_optimizer,
61    },
62    // program_data::ModuleInfo,
63    timing::Timings,
64    util::base54::CharFreq,
65};
66
67#[macro_use]
68mod macros;
69mod compress;
70mod debug;
71pub mod eval;
72#[doc(hidden)]
73pub mod js;
74mod metadata;
75mod mode;
76pub mod option;
77mod pass;
78mod program_data;
79mod size_hint;
80pub mod timing;
81mod util;
82
83pub mod marks {
84    pub use swc_ecma_usage_analyzer::marks::Marks;
85}
86
87const DISABLE_BUGGY_PASSES: bool = true;
88
89pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);
90pub(crate) static HEAVY_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 8);
91pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);
92
93pub fn optimize(
94    mut n: Program,
95    _cm: Lrc<SourceMap>,
96    comments: Option<&dyn Comments>,
97    mut timings: Option<&mut Timings>,
98    options: &MinifyOptions,
99    extra: &ExtraOptions,
100) -> Program {
101    let _timer = timer!("minify");
102
103    let mut marks = Marks::new();
104    marks.top_level_ctxt = SyntaxContext::empty().apply_mark(extra.top_level_mark);
105    marks.unresolved_mark = extra.unresolved_mark;
106
107    debug_assert_valid(&n);
108
109    if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
110        let _timer = timer!("inline global defs");
111        // Apply global defs.
112        //
113        // As terser treats `CONFIG['VALUE']` and `CONFIG.VALUE` differently, we don't
114        // have to see if optimized code matches global definition and we can run
115        // this at startup.
116
117        if !defs.is_empty() {
118            let defs = defs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
119            n.visit_mut_with(&mut global_defs::globals_defs(
120                defs,
121                extra.unresolved_mark,
122                extra.top_level_mark,
123            ));
124        }
125    }
126
127    if let Some(_options) = &options.compress {
128        let _timer = timer!("precompress");
129
130        n.visit_mut_with(&mut precompress_optimizer(ExprCtx {
131            unresolved_ctxt: SyntaxContext::empty().apply_mark(marks.unresolved_mark),
132            is_unresolved_ref_safe: false,
133            in_strict: false,
134            remaining_depth: 6,
135        }));
136        debug_assert_valid(&n);
137    }
138
139    if options.compress.is_some() {
140        n.visit_mut_with(&mut info_marker(
141            options.compress.as_ref(),
142            comments,
143            marks,
144            // extra.unresolved_mark,
145        ));
146        debug_assert_valid(&n);
147    }
148
149    if options.wrap {
150        // TODO: wrap_common_js
151        // toplevel = toplevel.wrap_commonjs(options.wrap);
152    }
153
154    if options.enclose {
155        // TODO: enclose
156        // toplevel = toplevel.wrap_enclose(options.enclose);
157    }
158    if let Some(ref mut t) = timings {
159        t.section("compress");
160    }
161    if let Some(options) = &options.compress {
162        if options.unused {
163            perform_dce(&mut n, options, extra);
164            debug_assert_valid(&n);
165        }
166    }
167
168    // We don't need validation.
169
170    if let Some(ref mut _t) = timings {
171        // TODO: store `rename`
172    }
173
174    // Noop.
175    // https://github.com/mishoo/UglifyJS2/issues/2794
176    if options.rename && DISABLE_BUGGY_PASSES {
177        // toplevel.figure_out_scope(options.mangle);
178        // TODO: Pass `options.mangle` to name expander.
179        // n.visit_mut_with(&mut name_expander());
180    }
181
182    if let Some(ref mut t) = timings {
183        t.section("compress");
184    }
185    if let Some(c) = &options.compress {
186        {
187            let _timer = timer!("compress ast");
188
189            n.mutate(&mut compressor(
190                marks,
191                c,
192                options.mangle.as_ref(),
193                &Minification,
194            ))
195        }
196
197        // Again, we don't need to validate ast
198
199        let _timer = timer!("postcompress");
200
201        n.visit_mut_with(&mut postcompress_optimizer(c));
202
203        n.visit_mut_with(&mut pure_optimizer(
204            c,
205            marks,
206            PureOptimizerConfig {
207                force_str_for_tpl: Minification.force_str_for_tpl(),
208                enable_join_vars: true,
209            },
210        ));
211    }
212
213    if let Some(ref mut _t) = timings {
214        // TODO: store `scope`
215    }
216    if options.mangle.is_some() {
217        // toplevel.figure_out_scope(options.mangle);
218    }
219
220    if let Some(ref mut t) = timings {
221        t.section("mangle");
222    }
223
224    if let Some(mangle) = &options.mangle {
225        let _timer = timer!("mangle names");
226        // TODO: base54.reset();
227
228        let preserved = idents_to_preserve(mangle, marks, &n);
229
230        let chars = if !mangle.disable_char_freq {
231            CharFreq::compute(
232                &n,
233                &preserved,
234                SyntaxContext::empty().apply_mark(marks.unresolved_mark),
235            )
236            .compile()
237        } else {
238            CharFreq::default().compile()
239        };
240
241        mangle_names(
242            &mut n,
243            mangle,
244            preserved,
245            chars,
246            extra.top_level_mark,
247            extra.mangle_name_cache.clone(),
248        );
249
250        if let Some(property_mangle_options) = &mangle.props {
251            mangle_properties(&mut n, property_mangle_options.clone(), chars);
252        }
253    }
254
255    n.visit_mut_with(&mut merge_exports());
256
257    if let Some(ref mut t) = timings {
258        t.section("hygiene");
259        t.end_section();
260    }
261
262    n
263}
264
265fn perform_dce(m: &mut Program, options: &CompressOptions, extra: &ExtraOptions) {
266    let _timer = timer!("remove dead code");
267
268    let mut visitor = swc_ecma_transforms_optimization::simplify::dce::dce(
269        swc_ecma_transforms_optimization::simplify::dce::Config {
270            module_mark: None,
271            top_level: options.top_level(),
272            top_retain: options.top_retain.clone(),
273            preserve_imports_with_side_effects: true,
274        },
275        extra.unresolved_mark,
276    );
277
278    loop {
279        #[cfg(feature = "debug")]
280        let start = crate::debug::dump(&*m, false);
281
282        m.visit_mut_with(&mut visitor);
283
284        #[cfg(feature = "debug")]
285        if visitor.changed() {
286            let src = crate::debug::dump(&*m, false);
287            tracing::debug!(
288                "===== Before DCE =====\n{}\n===== After DCE =====\n{}",
289                start,
290                src
291            );
292        }
293
294        if !visitor.changed() {
295            break;
296        }
297
298        visitor.reset();
299    }
300
301    debug_assert_valid(&*m);
302}