1use std::sync::Arc;
2
3use compat::es2015::regenerator;
4use either::Either;
5use rustc_hash::FxHashMap;
6use swc_atoms::Atom;
7use swc_common::{
8 comments::Comments, errors::Handler, sync::Lrc, util::take::Take, FileName, Mark, SourceMap,
9};
10use swc_ecma_ast::{EsVersion, Module, Pass, Script};
11use swc_ecma_minifier::option::{terser::TerserTopLevelOptions, MinifyOptions};
12use swc_ecma_parser::Syntax;
13use swc_ecma_transforms::{
14 compat,
15 feature::{enable_available_feature_from_es_version, FeatureFlag},
16 fixer::{fixer, paren_remover},
17 helpers,
18 hygiene::{self, hygiene_with_config},
19 modules::{self, path::ImportResolver},
20 optimization::const_modules,
21 resolver, Assumptions,
22};
23use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
24use swc_visit::Optional;
25
26use crate::config::{GlobalPassOption, JsMinifyOptions, ModuleConfig};
27
28pub struct PassBuilder<'a, 'b, P: Pass> {
30 cm: &'a Arc<SourceMap>,
31 handler: &'b Handler,
32 env: Option<swc_ecma_preset_env::Config>,
33 pass: P,
34 top_level_mark: Mark,
36
37 unresolved_mark: Mark,
39
40 target: EsVersion,
41 loose: bool,
42 assumptions: Assumptions,
43 hygiene: Option<hygiene::Config>,
44 fixer: bool,
45 inject_helpers: bool,
46 minify: Option<JsMinifyOptions>,
47 regenerator: regenerator::Config,
48}
49
50impl<'a, 'b, P: Pass> PassBuilder<'a, 'b, P> {
51 pub fn new(
52 cm: &'a Arc<SourceMap>,
53 handler: &'b Handler,
54 loose: bool,
55 assumptions: Assumptions,
56 top_level_mark: Mark,
57 unresolved_mark: Mark,
58 pass: P,
59 ) -> Self {
60 PassBuilder {
61 cm,
62 handler,
63 env: None,
64 pass,
65 top_level_mark,
66 unresolved_mark,
67 target: EsVersion::Es5,
68 loose,
69 assumptions,
70 hygiene: Some(Default::default()),
71 fixer: true,
72 inject_helpers: true,
73 minify: None,
74 regenerator: Default::default(),
75 }
76 }
77
78 pub fn then<N>(self, next: N) -> PassBuilder<'a, 'b, (P, N)>
79 where
80 N: Pass,
81 {
82 let pass = (self.pass, next);
83 PassBuilder {
84 cm: self.cm,
85 handler: self.handler,
86 env: self.env,
87 pass,
88 top_level_mark: self.top_level_mark,
89 unresolved_mark: self.unresolved_mark,
90 target: self.target,
91 loose: self.loose,
92 assumptions: self.assumptions,
93 hygiene: self.hygiene,
94 fixer: self.fixer,
95 inject_helpers: self.inject_helpers,
96 minify: self.minify,
97 regenerator: self.regenerator,
98 }
99 }
100
101 pub fn skip_helper_injection(mut self, skip: bool) -> Self {
102 self.inject_helpers = !skip;
103 self
104 }
105
106 pub fn minify(mut self, options: Option<JsMinifyOptions>) -> Self {
107 self.minify = options;
108 self
109 }
110
111 pub fn fixer(mut self, enable: bool) -> Self {
113 self.fixer = enable;
114 self
115 }
116
117 pub fn hygiene(mut self, config: Option<hygiene::Config>) -> Self {
121 self.hygiene = config;
122 self
123 }
124
125 pub fn const_modules(
126 self,
127 globals: FxHashMap<Atom, FxHashMap<Atom, String>>,
128 ) -> PassBuilder<'a, 'b, (P, impl Pass)> {
129 let cm = self.cm.clone();
130 self.then(const_modules(cm, globals))
131 }
132
133 pub fn inline_globals(self, c: GlobalPassOption) -> PassBuilder<'a, 'b, (P, impl Pass)> {
134 let pass = c.build(self.cm, self.handler);
135 self.then(pass)
136 }
137
138 pub fn target(mut self, target: EsVersion) -> Self {
139 self.target = target;
140 self
141 }
142
143 pub fn preset_env(mut self, env: Option<swc_ecma_preset_env::Config>) -> Self {
144 self.env = env;
145 self
146 }
147
148 pub fn regenerator(mut self, config: regenerator::Config) -> Self {
149 self.regenerator = config;
150 self
151 }
152
153 pub fn finalize<'cmt>(
166 self,
167 syntax: Syntax,
168 module: Option<ModuleConfig>,
169 comments: Option<&'cmt dyn Comments>,
170 resolver: Option<(FileName, Arc<dyn ImportResolver>)>,
171 ) -> impl 'cmt + Pass
172 where
173 P: 'cmt,
174 {
175 let (need_analyzer, import_interop, ignore_dynamic) = match module {
176 Some(ModuleConfig::CommonJs(ref c)) => (true, c.import_interop(), c.ignore_dynamic),
177 Some(ModuleConfig::Amd(ref c)) => {
178 (true, c.config.import_interop(), c.config.ignore_dynamic)
179 }
180 Some(ModuleConfig::Umd(ref c)) => {
181 (true, c.config.import_interop(), c.config.ignore_dynamic)
182 }
183 Some(ModuleConfig::SystemJs(_))
184 | Some(ModuleConfig::Es6(..))
185 | Some(ModuleConfig::NodeNext(..))
186 | None => (false, true.into(), true),
187 };
188
189 let mut feature_flag = FeatureFlag::empty();
190
191 let compat_pass = if let Some(env) = self.env {
193 Either::Left(swc_ecma_preset_env::preset_env(
194 self.unresolved_mark,
195 comments,
196 env,
197 self.assumptions,
198 &mut feature_flag,
199 ))
200 } else {
201 let assumptions = self.assumptions;
202
203 feature_flag = enable_available_feature_from_es_version(self.target);
204
205 Either::Right((
206 Optional::new(
207 compat::class_fields_use_set::class_fields_use_set(assumptions.pure_getters),
208 assumptions.set_public_class_fields,
209 ),
210 Optional::new(
211 compat::es2022::es2022(
212 compat::es2022::Config {
213 class_properties: compat::es2022::class_properties::Config {
214 private_as_properties: assumptions.private_fields_as_properties,
215 constant_super: assumptions.constant_super,
216 set_public_fields: assumptions.set_public_class_fields,
217 no_document_all: assumptions.no_document_all,
218 pure_getter: assumptions.pure_getters,
219 },
220 },
221 self.unresolved_mark,
222 ),
223 should_enable(self.target, EsVersion::Es2022),
224 ),
225 Optional::new(
226 compat::es2021::es2021(),
227 should_enable(self.target, EsVersion::Es2021),
228 ),
229 Optional::new(
230 compat::es2020::es2020(
231 compat::es2020::Config {
232 nullish_coalescing: compat::es2020::nullish_coalescing::Config {
233 no_document_all: assumptions.no_document_all,
234 },
235 optional_chaining: compat::es2020::optional_chaining::Config {
236 no_document_all: assumptions.no_document_all,
237 pure_getter: assumptions.pure_getters,
238 },
239 },
240 self.unresolved_mark,
241 ),
242 should_enable(self.target, EsVersion::Es2020),
243 ),
244 Optional::new(
245 compat::es2019::es2019(),
246 should_enable(self.target, EsVersion::Es2019),
247 ),
248 Optional::new(
249 compat::es2018(compat::es2018::Config {
250 object_rest_spread: compat::es2018::object_rest_spread::Config {
251 no_symbol: assumptions.object_rest_no_symbols,
252 set_property: assumptions.set_spread_properties,
253 pure_getters: assumptions.pure_getters,
254 },
255 }),
256 should_enable(self.target, EsVersion::Es2018),
257 ),
258 Optional::new(
259 compat::es2017(
260 compat::es2017::Config {
261 async_to_generator: compat::es2017::async_to_generator::Config {
262 ignore_function_name: assumptions.ignore_function_name,
263 ignore_function_length: assumptions.ignore_function_length,
264 },
265 },
266 self.unresolved_mark,
267 ),
268 should_enable(self.target, EsVersion::Es2017),
269 ),
270 Optional::new(
271 compat::es2016(),
272 should_enable(self.target, EsVersion::Es2016),
273 ),
274 Optional::new(
275 compat::es2015(
276 self.unresolved_mark,
277 comments,
278 compat::es2015::Config {
279 classes: compat::es2015::classes::Config {
280 constant_super: assumptions.constant_super,
281 no_class_calls: assumptions.no_class_calls,
282 set_class_methods: assumptions.set_class_methods,
283 super_is_callable_constructor: assumptions
284 .super_is_callable_constructor,
285 },
286 computed_props: compat::es2015::computed_props::Config {
287 loose: self.loose,
288 },
289 for_of: compat::es2015::for_of::Config {
290 assume_array: false,
291 loose: self.loose,
292 },
293 spread: compat::es2015::spread::Config { loose: self.loose },
294 destructuring: compat::es2015::destructuring::Config {
295 loose: self.loose,
296 },
297 regenerator: self.regenerator,
298 template_literal: compat::es2015::template_literal::Config {
299 ignore_to_primitive: assumptions.ignore_to_primitive_hint,
300 mutable_template: assumptions.mutable_template_object,
301 },
302 parameters: compat::es2015::parameters::Config {
303 ignore_function_length: assumptions.ignore_function_length,
304 },
305 typescript: syntax.typescript(),
306 },
307 ),
308 should_enable(self.target, EsVersion::Es2015),
309 ),
310 Optional::new(
311 compat::es3(true),
312 cfg!(feature = "es3") && self.target == EsVersion::Es3,
313 ),
314 ))
315 };
316
317 let is_mangler_enabled = self
318 .minify
319 .as_ref()
320 .map(|v| v.mangle.is_obj() || v.mangle.is_true())
321 .unwrap_or(false);
322
323 (
324 self.pass,
325 Optional::new(
326 paren_remover(comments.map(|v| v as &dyn Comments)),
327 self.fixer,
328 ),
329 compat_pass,
330 Optional::new(
332 modules::import_analysis::import_analyzer(import_interop, ignore_dynamic),
333 need_analyzer,
334 ),
335 Optional::new(
336 helpers::inject_helpers(self.unresolved_mark),
337 self.inject_helpers,
338 ),
339 ModuleConfig::build(
340 self.cm.clone(),
341 comments,
342 module,
343 self.unresolved_mark,
344 feature_flag,
345 resolver,
346 ),
347 visit_mut_pass(MinifierPass {
348 options: self.minify,
349 cm: self.cm.clone(),
350 comments,
351 top_level_mark: self.top_level_mark,
352 }),
353 Optional::new(
354 hygiene_with_config(swc_ecma_transforms_base::hygiene::Config {
355 top_level_mark: self.top_level_mark,
356 ..self.hygiene.clone().unwrap_or_default()
357 }),
358 self.hygiene.is_some() && !is_mangler_enabled,
359 ),
360 Optional::new(fixer(comments.map(|v| v as &dyn Comments)), self.fixer),
361 )
362 }
363}
364
365struct MinifierPass<'a> {
366 options: Option<JsMinifyOptions>,
367 cm: Lrc<SourceMap>,
368 comments: Option<&'a dyn Comments>,
369 top_level_mark: Mark,
370}
371
372impl VisitMut for MinifierPass<'_> {
373 noop_visit_mut_type!(fail);
374
375 fn visit_mut_module(&mut self, m: &mut Module) {
376 if let Some(options) = &self.options {
377 let opts = MinifyOptions {
378 compress: options
379 .compress
380 .clone()
381 .unwrap_as_option(|default| match default {
382 Some(true) => Some(Default::default()),
383 _ => None,
384 })
385 .map(|mut v| {
386 if v.const_to_let.is_none() {
387 v.const_to_let = Some(true);
388 }
389 if v.toplevel.is_none() {
390 v.toplevel = Some(TerserTopLevelOptions::Bool(true));
391 }
392
393 v.into_config(self.cm.clone())
394 }),
395 mangle: options
396 .mangle
397 .clone()
398 .unwrap_as_option(|default| match default {
399 Some(true) => Some(Default::default()),
400 _ => None,
401 }),
402 ..Default::default()
403 };
404
405 if opts.compress.is_none() && opts.mangle.is_none() {
406 return;
407 }
408
409 m.visit_mut_with(&mut hygiene_with_config(
410 swc_ecma_transforms_base::hygiene::Config {
411 top_level_mark: self.top_level_mark,
412 ..Default::default()
413 },
414 ));
415
416 let unresolved_mark = Mark::new();
417 let top_level_mark = Mark::new();
418
419 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
420
421 m.map_with_mut(|m| {
422 swc_ecma_minifier::optimize(
423 m.into(),
424 self.cm.clone(),
425 self.comments.as_ref().map(|v| v as &dyn Comments),
426 None,
427 &opts,
428 &swc_ecma_minifier::option::ExtraOptions {
429 unresolved_mark,
430 top_level_mark,
431 mangle_name_cache: None,
432 },
433 )
434 .expect_module()
435 })
436 }
437 }
438
439 fn visit_mut_script(&mut self, m: &mut Script) {
440 if let Some(options) = &self.options {
441 let opts = MinifyOptions {
442 compress: options
443 .compress
444 .clone()
445 .unwrap_as_option(|default| match default {
446 Some(true) => Some(Default::default()),
447 _ => None,
448 })
449 .map(|mut v| {
450 if v.const_to_let.is_none() {
451 v.const_to_let = Some(true);
452 }
453
454 v.module = false;
455
456 v.into_config(self.cm.clone())
457 }),
458 mangle: options
459 .mangle
460 .clone()
461 .unwrap_as_option(|default| match default {
462 Some(true) => Some(Default::default()),
463 _ => None,
464 }),
465 ..Default::default()
466 };
467
468 if opts.compress.is_none() && opts.mangle.is_none() {
469 return;
470 }
471
472 m.visit_mut_with(&mut hygiene_with_config(
473 swc_ecma_transforms_base::hygiene::Config {
474 top_level_mark: self.top_level_mark,
475 ..Default::default()
476 },
477 ));
478
479 let unresolved_mark = Mark::new();
480 let top_level_mark = Mark::new();
481
482 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
483
484 m.map_with_mut(|m| {
485 swc_ecma_minifier::optimize(
486 m.into(),
487 self.cm.clone(),
488 self.comments.as_ref().map(|v| v as &dyn Comments),
489 None,
490 &opts,
491 &swc_ecma_minifier::option::ExtraOptions {
492 unresolved_mark,
493 top_level_mark,
494 mangle_name_cache: None,
495 },
496 )
497 .expect_script()
498 })
499 }
500 }
501}
502
503pub(crate) fn should_enable(target: EsVersion, feature: EsVersion) -> bool {
504 target < feature
505}