1use 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 #[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 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}