swc_ecma_minifier/option/
terser.rs

1//! Compatibility for terser config.
2
3use rustc_hash::FxHashMap;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use swc_atoms::Atom;
7use swc_common::{sync::Lrc, FileName, SourceMap, DUMMY_SP};
8use swc_ecma_ast::*;
9use swc_ecma_parser::parse_file_as_expr;
10use swc_ecma_utils::drop_span;
11
12use super::{default_passes, true_by_default, CompressOptions, TopLevelOptions};
13use crate::option::PureGetterOption;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17#[serde(untagged)]
18pub enum TerserEcmaVersion {
19    Num(usize),
20    Str(String),
21}
22
23impl Default for TerserEcmaVersion {
24    fn default() -> Self {
25        Self::Num(5)
26    }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30#[serde(deny_unknown_fields)]
31#[serde(untagged)]
32pub enum TerserPureGetterOption {
33    Bool(bool),
34    #[serde(rename = "strict")]
35    #[default]
36    Strict,
37    Str(String),
38}
39
40#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
41#[serde(deny_unknown_fields)]
42#[serde(untagged)]
43pub enum TerserInlineOption {
44    Bool(bool),
45    Num(u8),
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(deny_unknown_fields)]
50#[serde(untagged)]
51pub enum TerserTopLevelOptions {
52    Bool(bool),
53    Str(String),
54}
55
56#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
57#[serde(deny_unknown_fields)]
58#[serde(untagged)]
59pub enum TerserSequenceOptions {
60    Bool(bool),
61    Num(u8),
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(deny_unknown_fields)]
66#[serde(untagged)]
67pub enum TerserTopRetainOption {
68    Str(String),
69    Seq(Vec<Atom>),
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct TerserCompressorOptions {
75    #[serde(default)]
76    pub arguments: bool,
77
78    #[serde(default)]
79    pub arrows: Option<bool>,
80
81    #[serde(default)]
82    pub booleans: Option<bool>,
83
84    #[serde(default)]
85    pub booleans_as_integers: bool,
86
87    #[serde(default)]
88    pub collapse_vars: Option<bool>,
89
90    #[serde(default)]
91    pub comparisons: Option<bool>,
92
93    #[serde(default)]
94    pub computed_props: Option<bool>,
95
96    #[serde(default)]
97    pub conditionals: Option<bool>,
98
99    #[serde(default)]
100    pub dead_code: Option<bool>,
101
102    #[serde(default = "true_by_default")]
103    pub defaults: bool,
104
105    #[serde(default)]
106    pub directives: Option<bool>,
107
108    #[serde(default)]
109    pub drop_console: bool,
110
111    #[serde(default)]
112    pub drop_debugger: Option<bool>,
113
114    #[serde(default)]
115    pub ecma: TerserEcmaVersion,
116
117    #[serde(default)]
118    pub evaluate: Option<bool>,
119
120    #[serde(default)]
121    pub expression: bool,
122
123    #[serde(default)]
124    pub global_defs: FxHashMap<Atom, Value>,
125
126    #[serde(default)]
127    pub hoist_funs: bool,
128
129    #[serde(default)]
130    pub hoist_props: Option<bool>,
131
132    #[serde(default)]
133    pub hoist_vars: bool,
134
135    #[serde(default)]
136    pub ie8: bool,
137
138    #[serde(default)]
139    pub if_return: Option<bool>,
140
141    #[serde(default)]
142    pub inline: Option<TerserInlineOption>,
143
144    #[serde(default)]
145    pub join_vars: Option<bool>,
146
147    #[serde(default)]
148    pub keep_classnames: bool,
149
150    #[serde(default = "true_by_default")]
151    pub keep_fargs: bool,
152
153    #[serde(default)]
154    pub keep_fnames: bool,
155
156    #[serde(default)]
157    pub keep_infinity: bool,
158
159    #[serde(default)]
160    pub loops: Option<bool>,
161    // module        : false,
162    #[serde(default)]
163    pub negate_iife: Option<bool>,
164
165    #[serde(default = "default_passes")]
166    pub passes: usize,
167
168    #[serde(default)]
169    pub properties: Option<bool>,
170
171    #[serde(default)]
172    pub pure_getters: TerserPureGetterOption,
173
174    #[serde(default)]
175    pub pure_funcs: Vec<String>,
176
177    #[serde(default)]
178    pub reduce_funcs: Option<bool>,
179
180    #[serde(default)]
181    pub reduce_vars: Option<bool>,
182
183    #[serde(default)]
184    pub sequences: Option<TerserSequenceOptions>,
185
186    #[serde(default)]
187    pub side_effects: Option<bool>,
188
189    #[serde(default)]
190    pub switches: Option<bool>,
191
192    #[serde(default)]
193    pub top_retain: Option<TerserTopRetainOption>,
194
195    #[serde(default)]
196    pub toplevel: Option<TerserTopLevelOptions>,
197
198    #[serde(default)]
199    pub typeofs: Option<bool>,
200
201    #[serde(default)]
202    #[serde(rename = "unsafe")]
203    pub unsafe_passes: bool,
204
205    #[serde(default)]
206    pub unsafe_arrows: bool,
207
208    #[serde(default)]
209    pub unsafe_comps: bool,
210
211    #[serde(default)]
212    #[serde(rename = "unsafe_Function")]
213    pub unsafe_function: bool,
214
215    #[serde(default)]
216    pub unsafe_math: bool,
217
218    #[serde(default)]
219    pub unsafe_symbols: bool,
220
221    #[serde(default)]
222    pub unsafe_methods: bool,
223
224    #[serde(default)]
225    pub unsafe_proto: bool,
226
227    #[serde(default)]
228    pub unsafe_regexp: bool,
229
230    #[serde(default)]
231    pub unsafe_undefined: bool,
232
233    #[serde(default)]
234    pub unused: Option<bool>,
235
236    #[serde(default)]
237    pub module: bool,
238
239    #[serde(default)]
240    pub const_to_let: Option<bool>,
241
242    #[serde(default)]
243    pub pristine_globals: Option<bool>,
244}
245
246impl_default!(TerserCompressorOptions);
247
248impl TerserCompressorOptions {
249    pub fn into_config(self, cm: Lrc<SourceMap>) -> CompressOptions {
250        CompressOptions {
251            arguments: self.arguments,
252            arrows: self.arrows.unwrap_or(self.defaults),
253            bools: self.booleans.unwrap_or(self.defaults),
254            bools_as_ints: self.booleans_as_integers,
255            collapse_vars: self.collapse_vars.unwrap_or(self.defaults),
256            comparisons: self.comparisons.unwrap_or(self.defaults),
257            computed_props: self.computed_props.unwrap_or(self.defaults),
258            conditionals: self.conditionals.unwrap_or(self.defaults),
259            dead_code: self.dead_code.unwrap_or(self.defaults),
260            directives: self.directives.unwrap_or(self.defaults),
261            drop_console: self.drop_console,
262            drop_debugger: self.drop_debugger.unwrap_or(self.defaults),
263            ecma: self.ecma.into(),
264            evaluate: self.evaluate.unwrap_or(self.defaults),
265            expr: self.expression,
266            global_defs: self
267                .global_defs
268                .into_iter()
269                .map(|(k, v)| {
270                    let parse = |input: String| {
271                        let fm = cm.new_source_file(FileName::Anon.into(), input);
272
273                        parse_file_as_expr(
274                            &fm,
275                            Default::default(),
276                            Default::default(),
277                            None,
278                            &mut Vec::new(),
279                        )
280                        .map(drop_span)
281                        .unwrap_or_else(|err| {
282                            panic!(
283                                "failed to parse `global_defs.{}` of minifier options: {:?}",
284                                k, err
285                            )
286                        })
287                    };
288                    let key = parse(if let Some(k) = k.strip_prefix('@') {
289                        k.to_string()
290                    } else {
291                        k.to_string()
292                    });
293
294                    (
295                        key,
296                        if k.starts_with('@') {
297                            parse(
298                                v.as_str()
299                                    .unwrap_or_else(|| {
300                                        panic!(
301                                            "Value of `global_defs.{}` must be a string literal: ",
302                                            k
303                                        )
304                                    })
305                                    .into(),
306                            )
307                        } else {
308                            value_to_expr(v)
309                        },
310                    )
311                })
312                .collect(),
313            hoist_fns: self.hoist_funs,
314            hoist_props: self.hoist_props.unwrap_or(self.defaults),
315            hoist_vars: self.hoist_vars,
316            ie8: self.ie8,
317            if_return: self.if_return.unwrap_or(self.defaults),
318            inline: self
319                .inline
320                .map(|v| match v {
321                    TerserInlineOption::Bool(v) => {
322                        if v {
323                            3
324                        } else {
325                            0
326                        }
327                    }
328                    TerserInlineOption::Num(n) => n,
329                })
330                .unwrap_or(if self.defaults { 3 } else { 0 }),
331            join_vars: self.join_vars.unwrap_or(self.defaults),
332            keep_classnames: self.keep_classnames,
333            keep_fargs: self.keep_fargs,
334            keep_fnames: self.keep_fnames,
335            keep_infinity: self.keep_infinity,
336            loops: self.loops.unwrap_or(self.defaults),
337            module: self.module,
338            negate_iife: self.negate_iife.unwrap_or(self.defaults),
339            passes: self.passes,
340            props: self.properties.unwrap_or(self.defaults),
341            pure_getters: match self.pure_getters {
342                TerserPureGetterOption::Bool(v) => PureGetterOption::Bool(v),
343                TerserPureGetterOption::Strict => PureGetterOption::Strict,
344                TerserPureGetterOption::Str(v) => {
345                    PureGetterOption::Str(v.split(',').map(From::from).collect())
346                }
347            },
348            reduce_fns: self.reduce_funcs.unwrap_or(self.defaults),
349            reduce_vars: self.reduce_vars.unwrap_or(self.defaults),
350            sequences: self
351                .sequences
352                .map(|v| match v {
353                    TerserSequenceOptions::Bool(v) => {
354                        if v {
355                            3
356                        } else {
357                            0
358                        }
359                    }
360                    TerserSequenceOptions::Num(v) => v,
361                })
362                .unwrap_or(if self.defaults { 3 } else { 0 }),
363            side_effects: self.side_effects.unwrap_or(self.defaults),
364            switches: self.switches.unwrap_or(self.defaults),
365            top_retain: self.top_retain.map(From::from).unwrap_or_default(),
366            top_level: self.toplevel.map(From::from),
367            typeofs: self.typeofs.unwrap_or(self.defaults),
368            unsafe_passes: self.unsafe_passes,
369            unsafe_arrows: self.unsafe_arrows,
370            unsafe_comps: self.unsafe_comps,
371            unsafe_function: self.unsafe_function,
372            unsafe_math: self.unsafe_math,
373            unsafe_symbols: self.unsafe_symbols,
374            unsafe_methods: self.unsafe_methods,
375            unsafe_proto: self.unsafe_proto,
376            unsafe_regexp: self.unsafe_regexp,
377            unsafe_undefined: self.unsafe_undefined,
378            unused: self.unused.unwrap_or(self.defaults),
379            const_to_let: self.const_to_let.unwrap_or(self.defaults),
380            pristine_globals: self.pristine_globals.unwrap_or(self.defaults),
381            pure_funcs: self
382                .pure_funcs
383                .into_iter()
384                .map(|input| {
385                    let fm = cm.new_source_file(FileName::Anon.into(), input);
386
387                    parse_file_as_expr(
388                        &fm,
389                        Default::default(),
390                        Default::default(),
391                        None,
392                        &mut Vec::new(),
393                    )
394                    .map(drop_span)
395                    .unwrap_or_else(|err| {
396                        panic!(
397                            "failed to parse `pure_funcs` of minifier options: {:?}",
398                            err
399                        )
400                    })
401                })
402                .collect(),
403        }
404    }
405}
406
407impl From<TerserTopLevelOptions> for TopLevelOptions {
408    fn from(c: TerserTopLevelOptions) -> Self {
409        match c {
410            TerserTopLevelOptions::Bool(v) => TopLevelOptions { functions: v },
411            TerserTopLevelOptions::Str(..) => {
412                // TODO
413                TopLevelOptions { functions: false }
414            }
415        }
416    }
417}
418
419impl From<TerserEcmaVersion> for EsVersion {
420    fn from(v: TerserEcmaVersion) -> Self {
421        match v {
422            TerserEcmaVersion::Num(v) => match v {
423                3 => EsVersion::Es3,
424                5 => EsVersion::Es5,
425                6 | 2015 => EsVersion::Es2015,
426                2016 => EsVersion::Es2016,
427                2017 => EsVersion::Es2017,
428                2018 => EsVersion::Es2018,
429                2019 => EsVersion::Es2019,
430                2020 => EsVersion::Es2020,
431                2021 => EsVersion::Es2021,
432                2022 => EsVersion::Es2022,
433                _ => {
434                    panic!("`{}` is not a valid ecmascript version", v)
435                }
436            },
437            TerserEcmaVersion::Str(v) => {
438                TerserEcmaVersion::Num(v.parse().expect("failed to parse version of ecmascript"))
439                    .into()
440            }
441        }
442    }
443}
444
445impl From<TerserTopRetainOption> for Vec<Atom> {
446    fn from(v: TerserTopRetainOption) -> Self {
447        match v {
448            TerserTopRetainOption::Str(s) => s
449                .split(',')
450                .filter(|s| s.trim() != "")
451                .map(|v| v.into())
452                .collect(),
453            TerserTopRetainOption::Seq(v) => v,
454        }
455    }
456}
457
458fn value_to_expr(v: Value) -> Box<Expr> {
459    match v {
460        Value::Null => Lit::Null(Null { span: DUMMY_SP }).into(),
461        Value::Bool(value) => Lit::Bool(Bool {
462            span: DUMMY_SP,
463            value,
464        })
465        .into(),
466        Value::Number(v) => {
467            trace_op!("Creating a numeric literal from value");
468
469            Lit::Num(Number {
470                span: DUMMY_SP,
471                value: v.as_f64().unwrap(),
472                raw: None,
473            })
474            .into()
475        }
476        Value::String(v) => {
477            let value: Atom = v.into();
478
479            Lit::Str(Str {
480                span: DUMMY_SP,
481                raw: None,
482                value,
483            })
484            .into()
485        }
486
487        Value::Array(arr) => {
488            let elems = arr
489                .into_iter()
490                .map(value_to_expr)
491                .map(|expr| Some(ExprOrSpread { spread: None, expr }))
492                .collect();
493            ArrayLit {
494                span: DUMMY_SP,
495                elems,
496            }
497            .into()
498        }
499
500        Value::Object(obj) => {
501            let props = obj
502                .into_iter()
503                .map(|(k, v)| (k, value_to_expr(v)))
504                .map(|(key, value)| KeyValueProp {
505                    key: PropName::Str(Str {
506                        span: DUMMY_SP,
507                        raw: None,
508                        value: key.into(),
509                    }),
510                    value,
511                })
512                .map(Prop::KeyValue)
513                .map(Box::new)
514                .map(PropOrSpread::Prop)
515                .collect();
516
517            ObjectLit {
518                span: DUMMY_SP,
519                props,
520            }
521            .into()
522        }
523    }
524}