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 module: self.module,
347 negate_iife: self.negate_iife.unwrap_or(self.defaults),
348 passes: self.passes,
349 props: self.properties.unwrap_or(self.defaults),
350 pure_getters: match self.pure_getters {
351 TerserPureGetterOption::Bool(v) => PureGetterOption::Bool(v),
352 TerserPureGetterOption::Strict => PureGetterOption::Strict,
353 TerserPureGetterOption::Str(v) => {
354 PureGetterOption::Str(v.split(',').map(From::from).collect())
355 }
356 },
357 reduce_fns: self.reduce_funcs.unwrap_or(self.defaults),
358 reduce_vars: self.reduce_vars.unwrap_or(self.defaults),
359 sequences: self
360 .sequences
361 .map(|v| match v {
362 TerserSequenceOptions::Bool(v) => {
363 if v {
364 3
365 } else {
366 0
367 }
368 }
369 TerserSequenceOptions::Num(v) => v,
370 })
371 .unwrap_or(if self.defaults { 3 } else { 0 }),
372 side_effects: self.side_effects.unwrap_or(self.defaults),
373 switches: self.switches.unwrap_or(self.defaults),
374 top_retain: self.top_retain.map(From::from).unwrap_or_default(),
375 top_level: self.toplevel.map(From::from),
376 typeofs: self.typeofs.unwrap_or(self.defaults),
377 unsafe_passes: self.unsafe_passes,
378 unsafe_arrows: self.unsafe_arrows,
379 unsafe_comps: self.unsafe_comps,
380 unsafe_function: self.unsafe_function,
381 unsafe_math: self.unsafe_math,
382 unsafe_symbols: self.unsafe_symbols,
383 unsafe_methods: self.unsafe_methods,
384 unsafe_proto: self.unsafe_proto,
385 unsafe_regexp: self.unsafe_regexp,
386 unsafe_undefined: self.unsafe_undefined,
387 unused: self.unused.unwrap_or(self.defaults),
388 const_to_let: self.const_to_let.unwrap_or(self.defaults),
389 pristine_globals: self.pristine_globals.unwrap_or(self.defaults),
390 pure_funcs: self
391 .pure_funcs
392 .into_iter()
393 .map(|input| {
394 let fm = cm.new_source_file(FileName::Anon.into(), input);
395
396 parse_file_as_expr(
397 &fm,
398 Default::default(),
399 Default::default(),
400 None,
401 &mut Vec::new(),
402 )
403 .map(drop_span)
404 .unwrap_or_else(|err| {
405 panic!("failed to parse `pure_funcs` of minifier options: {err:?}")
406 })
407 })
408 .collect(),
409 experimental: self
410 .experimental
411 .map(|experimental| {
412 CompressExperimentalOptions::from_terser_with_defaults(
413 experimental,
414 self.defaults,
415 )
416 })
417 .unwrap_or(CompressExperimentalOptions::from_defaults(self.defaults)),
418 }
419 }
420}
421
422impl From<TerserTopLevelOptions> for TopLevelOptions {
423 fn from(c: TerserTopLevelOptions) -> Self {
424 match c {
425 TerserTopLevelOptions::Bool(v) => TopLevelOptions { functions: v },
426 TerserTopLevelOptions::Str(..) => {
427 TopLevelOptions { functions: false }
429 }
430 }
431 }
432}
433
434impl From<TerserEcmaVersion> for EsVersion {
435 fn from(v: TerserEcmaVersion) -> Self {
436 match v {
437 TerserEcmaVersion::Num(v) => match v {
438 3 => EsVersion::Es3,
439 5 => EsVersion::Es5,
440 6 | 2015 => EsVersion::Es2015,
441 2016 => EsVersion::Es2016,
442 2017 => EsVersion::Es2017,
443 2018 => EsVersion::Es2018,
444 2019 => EsVersion::Es2019,
445 2020 => EsVersion::Es2020,
446 2021 => EsVersion::Es2021,
447 2022 => EsVersion::Es2022,
448 _ => {
449 panic!("`{v}` is not a valid ecmascript version")
450 }
451 },
452 TerserEcmaVersion::Str(v) => {
453 TerserEcmaVersion::Num(v.parse().expect("failed to parse version of ecmascript"))
454 .into()
455 }
456 }
457 }
458}
459
460impl From<TerserTopRetainOption> for Vec<Atom> {
461 fn from(v: TerserTopRetainOption) -> Self {
462 match v {
463 TerserTopRetainOption::Str(s) => s
464 .split(',')
465 .filter(|s| s.trim() != "")
466 .map(|v| v.into())
467 .collect(),
468 TerserTopRetainOption::Seq(v) => v,
469 }
470 }
471}
472
473fn value_to_expr(v: Value) -> Box<Expr> {
474 match v {
475 Value::Null => Lit::Null(Null { span: DUMMY_SP }).into(),
476 Value::Bool(value) => Lit::Bool(Bool {
477 span: DUMMY_SP,
478 value,
479 })
480 .into(),
481 Value::Number(v) => {
482 trace_op!("Creating a numeric literal from value");
483
484 Lit::Num(Number {
485 span: DUMMY_SP,
486 value: v.as_f64().unwrap(),
487 raw: None,
488 })
489 .into()
490 }
491 Value::String(v) => {
492 let value: Atom = v.into();
493
494 Lit::Str(Str {
495 span: DUMMY_SP,
496 raw: None,
497 value,
498 })
499 .into()
500 }
501
502 Value::Array(arr) => {
503 let elems = arr
504 .into_iter()
505 .map(value_to_expr)
506 .map(|expr| Some(ExprOrSpread { spread: None, expr }))
507 .collect();
508 ArrayLit {
509 span: DUMMY_SP,
510 elems,
511 }
512 .into()
513 }
514
515 Value::Object(obj) => {
516 let props = obj
517 .into_iter()
518 .map(|(k, v)| (k, value_to_expr(v)))
519 .map(|(key, value)| KeyValueProp {
520 key: PropName::Str(Str {
521 span: DUMMY_SP,
522 raw: None,
523 value: key.into(),
524 }),
525 value,
526 })
527 .map(Prop::KeyValue)
528 .map(Box::new)
529 .map(PropOrSpread::Prop)
530 .collect();
531
532 ObjectLit {
533 span: DUMMY_SP,
534 props,
535 }
536 .into()
537 }
538 }
539}