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