1use std::{
2 collections::HashMap,
3 env,
4 path::{Path, PathBuf},
5 sync::Arc,
6};
7
8use anyhow::{bail, Context, Error};
9use bytes_str::BytesStr;
10use dashmap::DashMap;
11use either::Either;
12use indexmap::IndexMap;
13use once_cell::sync::Lazy;
14use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
15use serde::{Deserialize, Serialize};
16use swc_atoms::Atom;
17#[allow(unused)]
18use swc_common::plugin::metadata::TransformPluginMetadataContext;
19use swc_common::{
20 comments::{Comments, SingleThreadedComments},
21 errors::Handler,
22 FileName, Mark, SourceMap,
23};
24pub use swc_compiler_base::SourceMapsConfig;
25pub use swc_config::is_module::IsModule;
26use swc_config::{
27 file_pattern::FilePattern,
28 merge::Merge,
29 types::{BoolConfig, BoolOr, BoolOrDataConfig, MergingOption},
30};
31use swc_ecma_ast::{noop_pass, EsVersion, Expr, Pass, Program};
32use swc_ecma_ext_transforms::jest;
33#[cfg(feature = "lint")]
34use swc_ecma_lints::{
35 config::LintConfig,
36 rules::{lint_pass, LintParams},
37};
38use swc_ecma_loader::resolvers::{
39 lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
40};
41pub use swc_ecma_minifier::js::*;
42use swc_ecma_minifier::option::terser::TerserTopLevelOptions;
43use swc_ecma_parser::{parse_file_as_expr, Syntax, TsSyntax};
44use swc_ecma_preset_env::{Caniuse, Feature};
45pub use swc_ecma_transforms::proposals::DecoratorVersion;
46use swc_ecma_transforms::{
47 fixer::{fixer, paren_remover},
48 helpers,
49 hygiene::{self, hygiene_with_config},
50 modules::{
51 self,
52 path::{ImportResolver, NodeImportResolver, Resolver},
53 rewriter::import_rewriter,
54 util, EsModuleConfig,
55 },
56 optimization::{const_modules, json_parse, simplifier},
57 proposals::{
58 decorators, explicit_resource_management::explicit_resource_management,
59 export_default_from, import_attributes,
60 },
61 react::{self, default_pragma, default_pragma_frag},
62 resolver,
63 typescript::{self, TsImportExportAssignConfig},
64 Assumptions,
65};
66use swc_ecma_transforms_compat::es2015::regenerator;
67use swc_ecma_transforms_optimization::{
68 inline_globals,
69 simplify::{dce::Config as DceConfig, Config as SimplifyConfig},
70 GlobalExprMap,
71};
72use swc_ecma_utils::NodeIgnoringSpan;
73use swc_ecma_visit::{visit_mut_pass, VisitMutWith};
74use swc_visit::Optional;
75
76pub use crate::plugin::PluginConfig;
77use crate::{
78 builder::MinifierPass, dropped_comments_preserver::dropped_comments_preserver,
79 SwcImportResolver,
80};
81
82#[cfg(test)]
83mod tests;
84
85#[cfg(feature = "plugin")]
86pub static PLUGIN_MODULE_CACHE: Lazy<swc_plugin_runner::cache::PluginModuleCache> =
88 Lazy::new(Default::default);
89
90#[cfg(feature = "plugin")]
100pub fn init_plugin_module_cache_once(
101 enable_fs_cache_store: bool,
102 fs_cache_store_root: Option<&str>,
103) {
104 PLUGIN_MODULE_CACHE.inner.get_or_init(|| {
105 parking_lot::Mutex::new(swc_plugin_runner::cache::PluginModuleCache::create_inner(
106 enable_fs_cache_store,
107 fs_cache_store_root,
108 ))
109 });
110}
111
112#[derive(Default, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct ParseOptions {
115 #[serde(default)]
116 pub comments: bool,
117 #[serde(flatten)]
118 pub syntax: Syntax,
119
120 #[serde(default)]
121 pub is_module: IsModule,
122
123 #[serde(default)]
124 pub target: EsVersion,
125}
126
127#[derive(Debug, Clone, Default, Deserialize)]
128#[serde(deny_unknown_fields, rename_all = "camelCase")]
129pub struct Options {
130 #[serde(flatten)]
131 pub config: Config,
132
133 #[serde(skip_deserializing, default)]
134 pub skip_helper_injection: bool,
135
136 #[serde(skip_deserializing, default)]
137 pub disable_hygiene: bool,
138
139 #[serde(skip_deserializing, default)]
140 pub disable_fixer: bool,
141
142 #[serde(skip_deserializing, default)]
143 pub top_level_mark: Option<Mark>,
144
145 #[serde(skip_deserializing, default)]
146 pub unresolved_mark: Option<Mark>,
147
148 #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
149 #[serde(default = "default_cwd")]
150 pub cwd: PathBuf,
151
152 #[serde(default)]
153 pub caller: Option<CallerOptions>,
154
155 #[serde(default)]
156 pub filename: String,
157
158 #[serde(default)]
159 pub config_file: Option<ConfigFile>,
160
161 #[serde(default)]
162 pub root: Option<PathBuf>,
163
164 #[serde(default)]
165 pub root_mode: RootMode,
166
167 #[serde(default = "default_swcrc")]
168 pub swcrc: bool,
169
170 #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
171 #[serde(default)]
172 pub swcrc_roots: Option<PathBuf>,
173
174 #[serde(default = "default_env_name")]
175 pub env_name: String,
176
177 #[serde(default)]
178 pub source_maps: Option<SourceMapsConfig>,
179
180 #[serde(default)]
181 pub source_file_name: Option<String>,
182
183 #[serde(default)]
184 pub source_root: Option<String>,
185
186 #[serde(default)]
187 pub output_path: Option<PathBuf>,
188
189 #[serde(default)]
190 pub experimental: ExperimentalOptions,
191
192 #[serde(skip, default)]
193 pub runtime_options: RuntimeOptions,
194}
195
196#[derive(Clone, Debug)]
197pub struct RuntimeOptions {
198 #[cfg(feature = "plugin")]
199 pub(crate) plugin_runtime: Option<Arc<dyn swc_plugin_runner::runtime::Runtime>>,
200}
201
202impl RuntimeOptions {
203 #[cfg(feature = "plugin")]
204 pub fn plugin_runtime(
205 mut self,
206 plugin_runtime: Arc<dyn swc_plugin_runner::runtime::Runtime>,
207 ) -> Self {
208 self.plugin_runtime = Some(plugin_runtime);
209 self
210 }
211}
212
213#[allow(clippy::derivable_impls)]
214impl Default for RuntimeOptions {
215 fn default() -> Self {
216 RuntimeOptions {
217 #[cfg(all(feature = "plugin", feature = "plugin_backend_wasmer"))]
218 plugin_runtime: Some(Arc::new(swc_plugin_backend_wasmer::WasmerRuntime)),
219 #[cfg(all(feature = "plugin", not(feature = "plugin_backend_wasmer")))]
220 plugin_runtime: None,
221 }
222 }
223}
224
225#[derive(Debug, Clone, Default, Deserialize, Merge)]
226#[serde(deny_unknown_fields, rename_all = "camelCase")]
227pub struct ExperimentalOptions {
228 #[serde(default)]
229 pub error_format: Option<ErrorFormat>,
230}
231
232impl Options {
233 pub fn codegen_target(&self) -> Option<EsVersion> {
234 self.config.jsc.target
235 }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(untagged)]
240pub enum InputSourceMap {
241 Bool(bool),
242 Str(String),
243}
244
245impl Default for InputSourceMap {
246 fn default() -> Self {
247 InputSourceMap::Bool(true)
248 }
249}
250
251impl Options {
252 #[allow(clippy::too_many_arguments)]
256 pub fn build_as_input<'a, P>(
257 &self,
258 cm: &Arc<SourceMap>,
259 base: &FileName,
260 parse: impl FnOnce(Syntax, EsVersion, IsModule) -> Result<Program, Error>,
261 output_path: Option<&Path>,
262 source_root: Option<String>,
263 source_file_name: Option<String>,
264 source_map_ignore_list: Option<FilePattern>,
265
266 handler: &Handler,
267 config: Option<Config>,
268 comments: Option<&'a SingleThreadedComments>,
269 custom_before_pass: impl FnOnce(&Program) -> P,
270 ) -> Result<BuiltInput<Box<dyn 'a + Pass>>, Error>
271 where
272 P: 'a + Pass,
273 {
274 let mut cfg = self.config.clone();
275
276 cfg.merge(config.unwrap_or_default());
277
278 if let FileName::Real(base) = base {
279 cfg.adjust(base);
280 }
281
282 let is_module = cfg.is_module.unwrap_or_default();
283
284 let mut source_maps = self.source_maps.clone();
285 source_maps.merge(cfg.source_maps.clone());
286
287 let JscConfig {
288 assumptions,
289 transform,
290 syntax,
291 external_helpers,
292 target,
293 loose,
294 keep_class_names,
295 base_url,
296 paths,
297 minify: mut js_minify,
298 experimental,
299 #[cfg(feature = "lint")]
300 lints,
301 preserve_all_comments,
302 ..
303 } = cfg.jsc;
304 let loose = loose.into_bool();
305 let preserve_all_comments = preserve_all_comments.into_bool();
306 let keep_class_names = keep_class_names.into_bool();
307 let external_helpers = external_helpers.into_bool();
308
309 let mut assumptions = assumptions.unwrap_or_else(|| {
310 if loose {
311 Assumptions::all()
312 } else {
313 Assumptions::default()
314 }
315 });
316
317 let unresolved_mark = self.unresolved_mark.unwrap_or_default();
318 let top_level_mark = self.top_level_mark.unwrap_or_default();
319
320 if target.is_some() && cfg.env.is_some() {
321 bail!("`env` and `jsc.target` cannot be used together");
322 }
323
324 let es_version = target.unwrap_or_default();
325
326 let syntax = syntax.unwrap_or_default();
327
328 let mut program = parse(syntax, es_version, is_module)?;
329
330 let mut transform = transform.into_inner().unwrap_or_default();
331
332 if syntax.typescript() {
337 assumptions.set_class_methods |= !transform.use_define_for_class_fields.into_bool();
338 }
339
340 assumptions.set_public_class_fields |= !transform.use_define_for_class_fields.into_bool();
341
342 program.visit_mut_with(&mut resolver(
343 unresolved_mark,
344 top_level_mark,
345 syntax.typescript(),
346 ));
347
348 let default_top_level = program.is_module();
349
350 js_minify = js_minify.map(|mut c| {
351 let compress = c
352 .compress
353 .unwrap_as_option(|default| match default {
354 Some(true) => Some(Default::default()),
355 _ => None,
356 })
357 .map(|mut c| {
358 if c.toplevel.is_none() {
359 c.toplevel = Some(TerserTopLevelOptions::Bool(default_top_level));
360 }
361
362 if matches!(
363 cfg.module,
364 None | Some(ModuleConfig::Es6(..) | ModuleConfig::NodeNext(..))
365 ) {
366 c.module = true;
367 }
368
369 c
370 })
371 .map(BoolOrDataConfig::from_obj)
372 .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
373
374 let mangle = c
375 .mangle
376 .unwrap_as_option(|default| match default {
377 Some(true) => Some(Default::default()),
378 _ => None,
379 })
380 .map(|mut c| {
381 if c.top_level.is_none() {
382 c.top_level = Some(default_top_level);
383 }
384
385 c
386 })
387 .map(BoolOrDataConfig::from_obj)
388 .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
389
390 if c.toplevel.is_none() {
391 c.toplevel = Some(default_top_level);
392 }
393
394 JsMinifyOptions {
395 compress,
396 mangle,
397 ..c
398 }
399 });
400
401 if js_minify.is_some() && js_minify.as_ref().unwrap().keep_fnames {
402 js_minify = js_minify.map(|c| {
403 let compress = c
404 .compress
405 .unwrap_as_option(|default| match default {
406 Some(true) => Some(Default::default()),
407 _ => None,
408 })
409 .map(|mut c| {
410 c.keep_fnames = true;
411 c
412 })
413 .map(BoolOrDataConfig::from_obj)
414 .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
415 let mangle = c
416 .mangle
417 .unwrap_as_option(|default| match default {
418 Some(true) => Some(Default::default()),
419 _ => None,
420 })
421 .map(|mut c| {
422 c.keep_fn_names = true;
423 c
424 })
425 .map(BoolOrDataConfig::from_obj)
426 .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
427 JsMinifyOptions {
428 compress,
429 mangle,
430 ..c
431 }
432 });
433 }
434
435 if js_minify.is_some() && js_minify.as_ref().unwrap().keep_classnames {
436 js_minify = js_minify.map(|c| {
437 let compress = c
438 .compress
439 .unwrap_as_option(|default| match default {
440 Some(true) => Some(Default::default()),
441 _ => None,
442 })
443 .map(|mut c| {
444 c.keep_classnames = true;
445 c
446 })
447 .map(BoolOrDataConfig::from_obj)
448 .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
449 let mangle = c
450 .mangle
451 .unwrap_as_option(|default| match default {
452 Some(true) => Some(Default::default()),
453 _ => None,
454 })
455 .map(|mut c| {
456 c.keep_class_names = true;
457 c
458 })
459 .map(BoolOrDataConfig::from_obj)
460 .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
461 JsMinifyOptions {
462 compress,
463 mangle,
464 ..c
465 }
466 });
467 }
468
469 let preserve_comments = if preserve_all_comments {
470 BoolOr::Bool(true)
471 } else {
472 js_minify
473 .as_ref()
474 .map(|v| match v.format.comments.clone().into_inner() {
475 Some(v) => v,
476 None => BoolOr::Bool(true),
477 })
478 .unwrap_or_else(|| {
479 BoolOr::Data(if cfg.minify.into_bool() {
480 JsMinifyCommentOption::PreserveSomeComments
481 } else {
482 JsMinifyCommentOption::PreserveAllComments
483 })
484 })
485 };
486
487 if syntax.typescript() {
488 transform.legacy_decorator = true.into();
489 }
490 let optimizer = transform.optimizer;
491
492 let const_modules = {
493 let enabled = transform.const_modules.is_some();
494 let config = transform.const_modules.unwrap_or_default();
495
496 let globals = config.globals;
497 Optional::new(const_modules(cm.clone(), globals), enabled)
498 };
499
500 let json_parse_pass = {
501 optimizer
502 .as_ref()
503 .and_then(|v| v.jsonify)
504 .as_ref()
505 .map(|cfg| json_parse(cfg.min_cost))
506 };
507
508 let simplifier_pass = {
509 if let Some(ref opts) = optimizer.as_ref().and_then(|o| o.simplify) {
510 match opts {
511 SimplifyOption::Bool(allow_simplify) => {
512 if *allow_simplify {
513 Some(simplifier(unresolved_mark, Default::default()))
514 } else {
515 None
516 }
517 }
518 SimplifyOption::Json(cfg) => Some(simplifier(
519 unresolved_mark,
520 SimplifyConfig {
521 dce: DceConfig {
522 preserve_imports_with_side_effects: cfg
523 .preserve_imports_with_side_effects,
524 ..Default::default()
525 },
526 ..Default::default()
527 },
528 )),
529 }
530 } else {
531 None
532 }
533 };
534
535 let optimization = {
536 optimizer
537 .and_then(|o| o.globals)
538 .map(|opts| opts.build(cm, handler, unresolved_mark))
539 };
540
541 let pass = (
542 const_modules,
543 optimization,
544 Optional::new(export_default_from(), syntax.export_default_from()),
545 simplifier_pass,
546 json_parse_pass,
547 );
548
549 let import_export_assign_config = match cfg.module {
550 Some(ModuleConfig::Es6(..)) => TsImportExportAssignConfig::EsNext,
551 Some(ModuleConfig::CommonJs(..))
552 | Some(ModuleConfig::Amd(..))
553 | Some(ModuleConfig::Umd(..)) => TsImportExportAssignConfig::Preserve,
554 Some(ModuleConfig::NodeNext(..)) => TsImportExportAssignConfig::NodeNext,
555 _ => TsImportExportAssignConfig::Classic,
557 };
558
559 let verbatim_module_syntax = transform.verbatim_module_syntax.into_bool();
560 let ts_enum_is_mutable = transform.ts_enum_is_mutable.into_bool();
561
562 let charset = cfg.jsc.output.charset.or_else(|| {
563 if js_minify.as_ref()?.format.ascii_only {
564 Some(OutputCharset::Ascii)
565 } else {
566 None
567 }
568 });
569
570 let codegen_inline_script = js_minify.as_ref().is_some_and(|v| v.format.inline_script);
575
576 let preamble = if !cfg.jsc.output.preamble.is_empty() {
577 cfg.jsc.output.preamble
578 } else {
579 js_minify
580 .as_ref()
581 .map(|v| v.format.preamble.clone())
582 .unwrap_or_default()
583 };
584
585 let paths = paths.into_iter().collect();
586 let resolver = ModuleConfig::get_resolver(&base_url, paths, base, cfg.module.as_ref());
587
588 let target = es_version;
589 let inject_helpers = !self.skip_helper_injection;
590 let fixer_enabled = !self.disable_fixer;
591 let hygiene_config = if self.disable_hygiene {
592 None
593 } else {
594 Some(hygiene::Config {
595 keep_class_names,
596 ..Default::default()
597 })
598 };
599 let env = cfg.env.map(Into::into);
600
601 let (need_analyzer, import_interop, ignore_dynamic) = match cfg.module {
603 Some(ModuleConfig::CommonJs(ref c)) => (true, c.import_interop(), c.ignore_dynamic),
604 Some(ModuleConfig::Amd(ref c)) => {
605 (true, c.config.import_interop(), c.config.ignore_dynamic)
606 }
607 Some(ModuleConfig::Umd(ref c)) => {
608 (true, c.config.import_interop(), c.config.ignore_dynamic)
609 }
610 Some(ModuleConfig::SystemJs(_))
611 | Some(ModuleConfig::Es6(..))
612 | Some(ModuleConfig::NodeNext(..))
613 | None => (false, true.into(), true),
614 };
615
616 let feature_config = env
617 .as_ref()
618 .map(|e: &swc_ecma_preset_env::EnvConfig| e.get_feature_config());
619
620 let compat_pass = {
622 if let Some(env_config) = env {
623 Either::Left(swc_ecma_preset_env::transform_from_env(
624 unresolved_mark,
625 comments.map(|v| v as &dyn Comments),
626 env_config,
627 assumptions,
628 ))
629 } else {
630 Either::Right(swc_ecma_preset_env::transform_from_es_version(
631 unresolved_mark,
632 comments.map(|v| v as &dyn Comments),
633 target,
634 assumptions,
635 loose,
636 ))
637 }
638 };
639
640 let is_mangler_enabled = js_minify
641 .as_ref()
642 .map(|v| v.mangle.is_obj() || v.mangle.is_true())
643 .unwrap_or(false);
644
645 let built_pass = (
646 pass,
647 Optional::new(
648 paren_remover(comments.map(|v| v as &dyn Comments)),
649 fixer_enabled,
650 ),
651 compat_pass,
652 Optional::new(
654 modules::import_analysis::import_analyzer(import_interop, ignore_dynamic),
655 need_analyzer,
656 ),
657 Optional::new(helpers::inject_helpers(unresolved_mark), inject_helpers),
658 ModuleConfig::build(
659 cm.clone(),
660 comments.map(|v| v as &dyn Comments),
661 cfg.module,
662 unresolved_mark,
663 resolver.clone(),
664 |f| {
665 feature_config
666 .as_ref()
667 .map_or_else(|| target.caniuse(f), |env| env.caniuse(f))
668 },
669 ),
670 visit_mut_pass(MinifierPass {
671 options: js_minify,
672 cm: cm.clone(),
673 comments: comments.map(|v| v as &dyn Comments),
674 top_level_mark,
675 }),
676 Optional::new(
677 hygiene_with_config(swc_ecma_transforms_base::hygiene::Config {
678 top_level_mark,
679 ..hygiene_config.clone().unwrap_or_default()
680 }),
681 hygiene_config.is_some() && !is_mangler_enabled,
682 ),
683 Optional::new(fixer(comments.map(|v| v as &dyn Comments)), fixer_enabled),
684 );
685
686 let keep_import_attributes = experimental.keep_import_attributes.into_bool();
687
688 #[cfg(feature = "plugin")]
689 let plugin_transforms: Box<dyn Pass> = {
690 let transform_filename = match base {
691 FileName::Real(path) => path.as_os_str().to_str().map(String::from),
692 FileName::Custom(filename) => Some(filename.to_owned()),
693 _ => None,
694 };
695 let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
696 transform_filename,
697 self.env_name.to_owned(),
698 None,
699 ));
700
701 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
705 {
706 let plugin_runtime = self
707 .runtime_options
708 .plugin_runtime
709 .clone()
710 .context("plugin runtime not configured")?;
711
712 if let Some(plugins) = &experimental.plugins {
713 crate::plugin::compile_wasm_plugins(
714 experimental.cache_root.as_deref(),
715 plugins,
716 &*plugin_runtime,
717 )
718 .context("Failed to compile wasm plugins")?;
719 }
720
721 Box::new(crate::plugin::plugins(
722 experimental.plugins,
723 experimental.plugin_env_vars,
724 transform_metadata_context,
725 comments.cloned(),
726 cm.clone(),
727 unresolved_mark,
728 plugin_runtime,
729 ))
730 }
731
732 #[cfg(all(feature = "plugin", target_arch = "wasm32"))]
737 {
738 handler.warn(
739 "Currently @swc/wasm does not support plugins, plugin transform will be \
740 skipped. Refer https://github.com/swc-project/swc/issues/3934 for the details.",
741 );
742
743 Box::new(noop_pass())
744 }
745 };
746
747 #[cfg(not(feature = "plugin"))]
748 let plugin_transforms: Box<dyn Pass> = {
749 if experimental.plugins.is_some() {
750 handler.warn(
751 "Plugin is not supported with current @swc/core. Plugin transform will be \
752 skipped.",
753 );
754 }
755 Box::new(noop_pass())
756 };
757
758 let mut plugin_transforms = Some(plugin_transforms);
759
760 let pass: Box<dyn Pass> = if experimental
761 .disable_builtin_transforms_for_internal_testing
762 .into_bool()
763 {
764 plugin_transforms.unwrap()
765 } else {
766 let decorator_pass: Box<dyn Pass> =
767 match transform.decorator_version.unwrap_or_default() {
768 DecoratorVersion::V202112 => Box::new(decorators(decorators::Config {
769 legacy: transform.legacy_decorator.into_bool(),
770 emit_metadata: transform.decorator_metadata.into_bool(),
771 use_define_for_class_fields: !assumptions.set_public_class_fields,
772 })),
773 DecoratorVersion::V202203 => Box::new(
774 swc_ecma_transforms::proposals::decorator_2022_03::decorator_2022_03(),
775 ),
776 DecoratorVersion::V202311 => todo!("2023-11 decorator"),
777 };
778 #[cfg(feature = "lint")]
779 let lint = {
780 use swc_common::SyntaxContext;
781 let disable_all_lints = experimental.disable_all_lints.into_bool();
782 let unresolved_ctxt = SyntaxContext::empty().apply_mark(unresolved_mark);
783 let top_level_ctxt = SyntaxContext::empty().apply_mark(top_level_mark);
784 Optional::new(
785 lint_pass(swc_ecma_lints::rules::all(LintParams {
786 program: &program,
787 lint_config: &lints,
788 top_level_ctxt,
789 unresolved_ctxt,
790 es_version,
791 source_map: cm.clone(),
792 })),
793 !disable_all_lints,
794 )
795 };
796 Box::new((
797 (
798 if experimental.run_plugin_first.into_bool() {
799 plugin_transforms.take()
800 } else {
801 None
802 },
803 #[cfg(feature = "lint")]
804 lint,
805 Optional::new(decorator_pass, syntax.decorators()),
807 Optional::new(
808 explicit_resource_management(),
809 syntax.explicit_resource_management(),
810 ),
811 ),
812 (
815 Optional::new(import_attributes(), !keep_import_attributes),
816 {
817 let native_class_properties = !assumptions.set_public_class_fields
818 && feature_config.as_ref().map_or_else(
819 || target.caniuse(Feature::ClassProperties),
820 |env| env.caniuse(Feature::ClassProperties),
821 );
822
823 let ts_config = typescript::Config {
824 import_export_assign_config,
825 verbatim_module_syntax,
826 native_class_properties,
827 ts_enum_is_mutable,
828 ..Default::default()
829 };
830
831 (
832 Optional::new(
833 typescript::typescript(ts_config, unresolved_mark, top_level_mark),
834 syntax.typescript() && !syntax.jsx(),
835 ),
836 Optional::new(
837 typescript::tsx::<Option<&dyn Comments>>(
838 cm.clone(),
839 ts_config,
840 typescript::TsxConfig {
841 pragma: Some(
842 transform
843 .react
844 .pragma
845 .clone()
846 .unwrap_or_else(default_pragma),
847 ),
848 pragma_frag: Some(
849 transform
850 .react
851 .pragma_frag
852 .clone()
853 .unwrap_or_else(default_pragma_frag),
854 ),
855 },
856 comments.map(|v| v as _),
857 unresolved_mark,
858 top_level_mark,
859 ),
860 syntax.typescript() && syntax.jsx(),
861 ),
862 )
863 },
864 ),
865 (
866 plugin_transforms.take(),
867 custom_before_pass(&program),
868 Optional::new(
870 react::react::<&dyn Comments>(
871 cm.clone(),
872 comments.map(|v| v as _),
873 transform.react,
874 top_level_mark,
875 unresolved_mark,
876 ),
877 syntax.jsx(),
878 ),
879 built_pass,
880 Optional::new(jest::jest(), transform.hidden.jest.into_bool()),
881 Optional::new(
882 dropped_comments_preserver(comments.cloned()),
883 preserve_all_comments,
884 ),
885 ),
886 ))
887 };
888
889 Ok(BuiltInput {
890 program,
891 minify: cfg.minify.into_bool(),
892 pass,
893 external_helpers,
894 syntax,
895 target: es_version,
896 is_module,
897 source_maps: source_maps.unwrap_or(SourceMapsConfig::Bool(false)),
898 inline_sources_content: cfg.inline_sources_content.into_bool(),
899 input_source_map: cfg.input_source_map.clone().unwrap_or_default(),
900 output_path: output_path.map(|v| v.to_path_buf()),
901 source_root,
902 source_file_name,
903 source_map_ignore_list,
904 comments: comments.cloned(),
905 preserve_comments,
906 emit_source_map_columns: cfg.emit_source_map_columns.into_bool(),
907 output: JscOutputConfig {
908 charset,
909 preamble,
910 ..cfg.jsc.output
911 },
912 emit_assert_for_import_attributes: experimental
913 .emit_assert_for_import_attributes
914 .into_bool(),
915 codegen_inline_script,
916 emit_isolated_dts: experimental.emit_isolated_dts.into_bool(),
917 unresolved_mark,
918 resolver,
919 })
920 }
921}
922
923#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
924pub enum RootMode {
925 #[default]
926 #[serde(rename = "root")]
927 Root,
928 #[serde(rename = "upward")]
929 Upward,
930 #[serde(rename = "upward-optional")]
931 UpwardOptional,
932}
933
934const fn default_swcrc() -> bool {
935 true
936}
937
938#[derive(Debug, Clone, Serialize, Deserialize)]
939#[serde(untagged)]
940pub enum ConfigFile {
941 Bool(bool),
942 Str(String),
943}
944
945impl Default for ConfigFile {
946 fn default() -> Self {
947 ConfigFile::Bool(true)
948 }
949}
950
951#[derive(Debug, Default, Clone, Serialize, Deserialize)]
952#[serde(rename_all = "camelCase")]
953pub struct CallerOptions {
954 pub name: String,
955}
956
957#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
958fn default_cwd() -> PathBuf {
959 static CWD: Lazy<PathBuf> = Lazy::new(|| ::std::env::current_dir().unwrap());
960
961 CWD.clone()
962}
963
964#[derive(Debug, Clone, Deserialize)]
966#[serde(untagged, rename = "swcrc")]
967#[allow(clippy::large_enum_variant)]
968pub enum Rc {
969 Single(Config),
970 Multi(Vec<Config>),
971}
972
973impl Default for Rc {
974 fn default() -> Self {
975 Rc::Multi(vec![
976 Config {
977 env: None,
978 test: None,
979 exclude: Some(FileMatcher::Pattern(FilePattern::Regex("\\.tsx?$".into()))),
980 jsc: JscConfig {
981 syntax: Some(Default::default()),
982 ..Default::default()
983 },
984 ..Default::default()
985 },
986 Config {
987 env: None,
988 test: Some(FileMatcher::Pattern(FilePattern::Regex("\\.tsx$".into()))),
989 exclude: None,
990 jsc: JscConfig {
991 syntax: Some(Syntax::Typescript(TsSyntax {
992 tsx: true,
993 ..Default::default()
994 })),
995 ..Default::default()
996 },
997 ..Default::default()
998 },
999 Config {
1000 env: None,
1001 test: Some(FileMatcher::Pattern(FilePattern::Regex(
1002 "\\.(cts|mts)$".into(),
1003 ))),
1004 exclude: None,
1005 jsc: JscConfig {
1006 syntax: Some(Syntax::Typescript(TsSyntax {
1007 tsx: false,
1008 disallow_ambiguous_jsx_like: true,
1009 ..Default::default()
1010 })),
1011 ..Default::default()
1012 },
1013 ..Default::default()
1014 },
1015 Config {
1016 env: None,
1017 test: Some(FileMatcher::Pattern(FilePattern::Regex("\\.ts$".into()))),
1018 exclude: None,
1019 jsc: JscConfig {
1020 syntax: Some(Syntax::Typescript(TsSyntax {
1021 tsx: false,
1022 ..Default::default()
1023 })),
1024 ..Default::default()
1025 },
1026 ..Default::default()
1027 },
1028 ])
1029 }
1030}
1031
1032impl Rc {
1033 pub fn into_config(self, filename: Option<&Path>) -> Result<Option<Config>, Error> {
1035 let cs = match self {
1036 Rc::Single(mut c) => match filename {
1037 Some(filename) => {
1038 if c.matches(filename)? {
1039 c.adjust(filename);
1040
1041 return Ok(Some(c));
1042 } else {
1043 return Ok(None);
1044 }
1045 }
1046 None => return Ok(Some(c)),
1048 },
1049 Rc::Multi(cs) => cs,
1050 };
1051
1052 match filename {
1053 Some(filename) => {
1054 for mut c in cs {
1055 if c.matches(filename)? {
1056 c.adjust(filename);
1057
1058 return Ok(Some(c));
1059 }
1060 }
1061 }
1062 None => return Ok(Some(Config::default())),
1063 }
1064
1065 bail!(".swcrc exists but not matched")
1066 }
1067}
1068
1069#[derive(Debug, Default, Clone, Deserialize, Merge)]
1071#[serde(deny_unknown_fields, rename_all = "camelCase")]
1072pub struct Config {
1073 #[serde(default)]
1074 pub env: Option<swc_ecma_preset_env::Config>,
1075
1076 #[serde(default)]
1077 pub test: Option<FileMatcher>,
1078
1079 #[serde(default)]
1080 pub exclude: Option<FileMatcher>,
1081
1082 #[serde(default)]
1083 pub jsc: JscConfig,
1084
1085 #[serde(default)]
1086 pub module: Option<ModuleConfig>,
1087
1088 #[serde(default)]
1089 pub minify: BoolConfig<false>,
1090
1091 #[serde(default)]
1092 pub input_source_map: Option<InputSourceMap>,
1093
1094 #[serde(default)]
1096 pub source_maps: Option<SourceMapsConfig>,
1097
1098 #[serde(default)]
1099 pub source_map_ignore_list: Option<FilePattern>,
1100
1101 #[serde(default)]
1102 pub inline_sources_content: BoolConfig<true>,
1103
1104 #[serde(default)]
1105 pub emit_source_map_columns: BoolConfig<true>,
1106
1107 #[serde(default)]
1108 pub error: ErrorConfig,
1109
1110 #[serde(default)]
1111 pub is_module: Option<IsModule>,
1112
1113 #[serde(rename = "$schema")]
1114 pub schema: Option<String>,
1115}
1116
1117impl Config {
1118 pub fn adjust(&mut self, file: &Path) {
1124 if let Some(Syntax::Typescript(TsSyntax { tsx, dts, .. })) = &mut self.jsc.syntax {
1125 let is_dts = file
1126 .file_name()
1127 .and_then(|f| f.to_str())
1128 .map(|s| s.ends_with(".d.ts"))
1129 .unwrap_or(false);
1130
1131 if is_dts {
1132 *dts = true;
1133 }
1134
1135 if file.extension() == Some("tsx".as_ref()) {
1136 *tsx = true;
1137 } else if file.extension() == Some("ts".as_ref()) {
1138 *tsx = false;
1139 }
1140 }
1141 }
1142}
1143
1144#[derive(Debug, Clone, Serialize, Deserialize)]
1145#[serde(untagged)]
1146pub enum FileMatcher {
1147 None,
1148 Pattern(FilePattern),
1149 Multi(Vec<FileMatcher>),
1150}
1151
1152impl Default for FileMatcher {
1153 fn default() -> Self {
1154 Self::None
1155 }
1156}
1157
1158impl FileMatcher {
1159 pub fn matches(&self, filename: &Path) -> Result<bool, Error> {
1160 match self {
1161 FileMatcher::None => Ok(false),
1162
1163 FileMatcher::Pattern(re) => {
1164 let filename = if cfg!(target_os = "windows") {
1165 filename.to_string_lossy().replace('\\', "/")
1166 } else {
1167 filename.to_string_lossy().to_string()
1168 };
1169
1170 Ok(re.is_match(&filename))
1171 }
1172 FileMatcher::Multi(ref v) => {
1173 for m in v {
1175 if m.matches(filename)? {
1176 return Ok(true);
1177 }
1178 }
1179
1180 Ok(false)
1181 }
1182 }
1183 }
1184}
1185
1186impl Config {
1187 pub fn matches(&self, filename: &Path) -> Result<bool, Error> {
1188 if let Some(ref exclude) = self.exclude {
1189 if exclude.matches(filename)? {
1190 return Ok(false);
1191 }
1192 }
1193
1194 if let Some(ref include) = self.test {
1195 if include.matches(filename)? {
1196 return Ok(true);
1197 }
1198 return Ok(false);
1199 }
1200
1201 Ok(true)
1202 }
1203}
1204
1205#[non_exhaustive]
1207pub struct BuiltInput<P: Pass> {
1208 pub program: Program,
1209 pub pass: P,
1210 pub syntax: Syntax,
1211 pub target: EsVersion,
1212 pub minify: bool,
1215 pub external_helpers: bool,
1216 pub source_maps: SourceMapsConfig,
1217 pub input_source_map: InputSourceMap,
1218 pub is_module: IsModule,
1219 pub output_path: Option<PathBuf>,
1220
1221 pub source_root: Option<String>,
1222 pub source_file_name: Option<String>,
1223 pub source_map_ignore_list: Option<FilePattern>,
1224
1225 pub comments: Option<SingleThreadedComments>,
1226 pub preserve_comments: BoolOr<JsMinifyCommentOption>,
1227
1228 pub inline_sources_content: bool,
1229 pub emit_source_map_columns: bool,
1230
1231 pub output: JscOutputConfig,
1232 pub emit_assert_for_import_attributes: bool,
1233 pub codegen_inline_script: bool,
1234
1235 pub emit_isolated_dts: bool,
1236 pub unresolved_mark: Mark,
1237 pub resolver: Option<(FileName, Arc<dyn ImportResolver>)>,
1238}
1239
1240impl<P> BuiltInput<P>
1241where
1242 P: Pass,
1243{
1244 pub fn with_pass<N>(self, map: impl FnOnce(P) -> N) -> BuiltInput<N>
1245 where
1246 N: Pass,
1247 {
1248 BuiltInput {
1249 program: self.program,
1250 pass: map(self.pass),
1251 syntax: self.syntax,
1252 target: self.target,
1253 minify: self.minify,
1254 external_helpers: self.external_helpers,
1255 source_maps: self.source_maps,
1256 input_source_map: self.input_source_map,
1257 is_module: self.is_module,
1258 output_path: self.output_path,
1259 source_root: self.source_root,
1260 source_file_name: self.source_file_name,
1261 source_map_ignore_list: self.source_map_ignore_list,
1262 comments: self.comments,
1263 preserve_comments: self.preserve_comments,
1264 inline_sources_content: self.inline_sources_content,
1265 emit_source_map_columns: self.emit_source_map_columns,
1266 output: self.output,
1267 emit_assert_for_import_attributes: self.emit_assert_for_import_attributes,
1268 codegen_inline_script: self.codegen_inline_script,
1269 emit_isolated_dts: self.emit_isolated_dts,
1270 unresolved_mark: self.unresolved_mark,
1271 resolver: self.resolver,
1272 }
1273 }
1274}
1275
1276#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1278#[serde(deny_unknown_fields, rename_all = "camelCase")]
1279pub struct JscConfig {
1280 #[serde(default)]
1281 pub assumptions: Option<Assumptions>,
1282
1283 #[serde(rename = "parser", default)]
1284 pub syntax: Option<Syntax>,
1285
1286 #[serde(default)]
1287 pub transform: MergingOption<TransformConfig>,
1288
1289 #[serde(default)]
1290 pub external_helpers: BoolConfig<false>,
1291
1292 #[serde(default)]
1293 pub target: Option<EsVersion>,
1294
1295 #[serde(default)]
1296 pub loose: BoolConfig<false>,
1297
1298 #[serde(default)]
1299 pub keep_class_names: BoolConfig<false>,
1300
1301 #[serde(default)]
1302 pub base_url: PathBuf,
1303
1304 #[serde(default)]
1305 pub paths: Paths,
1306
1307 #[serde(default)]
1308 pub minify: Option<JsMinifyOptions>,
1309
1310 #[serde(default)]
1311 pub experimental: JscExperimental,
1312
1313 #[serde(default)]
1314 #[cfg(feature = "lint")]
1315 pub lints: LintConfig,
1316
1317 #[serde(default)]
1318 pub preserve_all_comments: BoolConfig<false>,
1319
1320 #[serde(default)]
1321 pub output: JscOutputConfig,
1322}
1323
1324#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1325#[serde(deny_unknown_fields, rename_all = "camelCase")]
1326pub struct JscOutputConfig {
1327 #[serde(default)]
1328 pub charset: Option<OutputCharset>,
1329
1330 #[serde(default)]
1331 pub preamble: String,
1332
1333 #[serde(default)]
1334 pub preserve_annotations: BoolConfig<false>,
1335
1336 #[serde(default)]
1337 pub source_map_url: Option<String>,
1338}
1339
1340#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1341#[serde(deny_unknown_fields, rename_all = "camelCase")]
1342pub enum OutputCharset {
1343 #[default]
1344 #[serde(rename = "utf8")]
1345 Utf8,
1346 #[serde(rename = "ascii")]
1347 Ascii,
1348}
1349
1350#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1352#[serde(deny_unknown_fields, rename_all = "camelCase")]
1353pub struct JscExperimental {
1354 #[serde(default)]
1356 pub plugins: Option<Vec<PluginConfig>>,
1357 #[serde(default)]
1358 pub plugin_env_vars: Option<Vec<Atom>>,
1359 #[serde(default, alias = "keepImportAssertions")]
1361 pub keep_import_attributes: BoolConfig<false>,
1362
1363 #[serde(default)]
1364 pub emit_assert_for_import_attributes: BoolConfig<false>,
1365 #[serde(default)]
1371 pub cache_root: Option<String>,
1372
1373 #[serde(default)]
1374 pub run_plugin_first: BoolConfig<false>,
1375
1376 #[serde(default)]
1377 pub disable_builtin_transforms_for_internal_testing: BoolConfig<false>,
1378
1379 #[serde(default)]
1383 pub emit_isolated_dts: BoolConfig<false>,
1384
1385 #[serde(default)]
1386 pub disable_all_lints: BoolConfig<true>,
1387}
1388
1389#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1390pub enum ErrorFormat {
1391 #[serde(rename = "json")]
1392 Json,
1393 #[serde(rename = "normal")]
1394 Normal,
1395}
1396
1397impl ErrorFormat {
1398 pub fn format(&self, err: &Error) -> String {
1399 match self {
1400 ErrorFormat::Normal => format!("{err:?}"),
1401 ErrorFormat::Json => {
1402 let mut map = serde_json::Map::new();
1403
1404 map.insert("message".into(), serde_json::Value::String(err.to_string()));
1405
1406 map.insert(
1407 "stack".into(),
1408 serde_json::Value::Array(
1409 err.chain()
1410 .skip(1)
1411 .map(|err| err.to_string())
1412 .map(serde_json::Value::String)
1413 .collect(),
1414 ),
1415 );
1416
1417 serde_json::to_string(&map).unwrap()
1418 }
1419 }
1420 }
1421}
1422
1423impl Default for ErrorFormat {
1424 fn default() -> Self {
1425 Self::Normal
1426 }
1427}
1428
1429pub type Paths = IndexMap<String, Vec<String>, FxBuildHasher>;
1431pub(crate) type CompiledPaths = Vec<(String, Vec<String>)>;
1432
1433#[derive(Debug, Clone, Serialize, Deserialize)]
1434#[serde(deny_unknown_fields, rename_all = "camelCase")]
1435#[serde(tag = "type")]
1436pub enum ModuleConfig {
1437 #[serde(rename = "commonjs")]
1438 CommonJs(modules::common_js::Config),
1439 #[serde(rename = "umd")]
1440 Umd(modules::umd::Config),
1441 #[serde(rename = "amd")]
1442 Amd(modules::amd::Config),
1443 #[serde(rename = "systemjs")]
1444 SystemJs(modules::system_js::Config),
1445 #[serde(rename = "es6")]
1446 Es6(EsModuleConfig),
1447 #[serde(rename = "nodenext")]
1448 NodeNext(EsModuleConfig),
1449}
1450
1451impl ModuleConfig {
1452 pub fn build<'cmt>(
1453 cm: Arc<SourceMap>,
1454 comments: Option<&'cmt dyn Comments>,
1455 config: Option<ModuleConfig>,
1456 unresolved_mark: Mark,
1457 resolver: Option<(FileName, Arc<dyn ImportResolver>)>,
1458 caniuse: impl (Fn(Feature) -> bool),
1459 ) -> Box<dyn Pass + 'cmt> {
1460 let resolver = if let Some((base, resolver)) = resolver {
1461 Resolver::Real { base, resolver }
1462 } else {
1463 Resolver::Default
1464 };
1465
1466 let support_block_scoping = caniuse(Feature::BlockScoping);
1467 let support_arrow = caniuse(Feature::ArrowFunctions);
1468
1469 match config {
1470 None | Some(ModuleConfig::Es6(..)) | Some(ModuleConfig::NodeNext(..)) => match resolver
1471 {
1472 Resolver::Default => Box::new(noop_pass()),
1473 Resolver::Real { base, resolver } => Box::new(import_rewriter(base, resolver)),
1474 },
1475 Some(ModuleConfig::CommonJs(config)) => Box::new(modules::common_js::common_js(
1476 resolver,
1477 unresolved_mark,
1478 config,
1479 modules::common_js::FeatureFlag {
1480 support_block_scoping,
1481 support_arrow,
1482 },
1483 )),
1484 Some(ModuleConfig::Umd(config)) => Box::new(modules::umd::umd(
1485 cm,
1486 resolver,
1487 unresolved_mark,
1488 config,
1489 modules::umd::FeatureFlag {
1490 support_block_scoping,
1491 },
1492 )),
1493 Some(ModuleConfig::Amd(config)) => Box::new(modules::amd::amd(
1494 resolver,
1495 unresolved_mark,
1496 config,
1497 modules::amd::FeatureFlag {
1498 support_block_scoping,
1499 support_arrow,
1500 },
1501 comments,
1502 )),
1503 Some(ModuleConfig::SystemJs(config)) => Box::new(modules::system_js::system_js(
1504 resolver,
1505 unresolved_mark,
1506 config,
1507 )),
1508 }
1509 }
1510
1511 pub fn get_resolver(
1512 base_url: &Path,
1513 paths: CompiledPaths,
1514 base: &FileName,
1515 config: Option<&ModuleConfig>,
1516 ) -> Option<(FileName, Arc<dyn ImportResolver>)> {
1517 let skip_resolver = base_url.as_os_str().is_empty() && paths.is_empty();
1518
1519 if skip_resolver {
1520 return None;
1521 }
1522
1523 let base = match base {
1524 FileName::Real(v) if !skip_resolver => {
1525 FileName::Real(v.canonicalize().unwrap_or_else(|_| v.to_path_buf()))
1526 }
1527 _ => base.clone(),
1528 };
1529
1530 let base_url = base_url.to_path_buf();
1531 let resolver = match config {
1532 None => build_resolver(base_url, paths, false, &util::Config::default_js_ext()),
1533 Some(ModuleConfig::Es6(config)) | Some(ModuleConfig::NodeNext(config)) => {
1534 build_resolver(
1535 base_url,
1536 paths,
1537 config.config.resolve_fully,
1538 &config.config.out_file_extension,
1539 )
1540 }
1541 Some(ModuleConfig::CommonJs(config)) => build_resolver(
1542 base_url,
1543 paths,
1544 config.resolve_fully,
1545 &config.out_file_extension,
1546 ),
1547 Some(ModuleConfig::Umd(config)) => build_resolver(
1548 base_url,
1549 paths,
1550 config.config.resolve_fully,
1551 &config.config.out_file_extension,
1552 ),
1553 Some(ModuleConfig::Amd(config)) => build_resolver(
1554 base_url,
1555 paths,
1556 config.config.resolve_fully,
1557 &config.config.out_file_extension,
1558 ),
1559 Some(ModuleConfig::SystemJs(config)) => build_resolver(
1560 base_url,
1561 paths,
1562 config.config.resolve_fully,
1563 &config.config.out_file_extension,
1564 ),
1565 };
1566
1567 Some((base, resolver))
1568 }
1569}
1570
1571#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1572#[serde(deny_unknown_fields, rename_all = "camelCase")]
1573pub struct TransformConfig {
1574 #[serde(default)]
1575 pub react: react::Options,
1576
1577 #[serde(default)]
1578 pub const_modules: Option<ConstModulesConfig>,
1579
1580 #[serde(default)]
1581 pub optimizer: Option<OptimizerConfig>,
1582
1583 #[serde(default)]
1584 pub legacy_decorator: BoolConfig<false>,
1585
1586 #[serde(default)]
1587 pub decorator_metadata: BoolConfig<false>,
1588
1589 #[serde(default)]
1590 pub hidden: HiddenTransformConfig,
1591
1592 #[serde(default)]
1593 pub regenerator: regenerator::Config,
1594
1595 #[serde(default)]
1596 #[deprecated]
1597 pub treat_const_enum_as_enum: BoolConfig<false>,
1598
1599 #[serde(default)]
1601 pub use_define_for_class_fields: BoolConfig<true>,
1602
1603 #[serde(default)]
1605 pub verbatim_module_syntax: BoolConfig<false>,
1606
1607 #[serde(default)]
1608 pub decorator_version: Option<DecoratorVersion>,
1609
1610 #[serde(default)]
1611 pub ts_enum_is_mutable: BoolConfig<false>,
1612}
1613
1614#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1615#[serde(deny_unknown_fields, rename_all = "camelCase")]
1616pub struct HiddenTransformConfig {
1617 #[serde(default)]
1618 pub jest: BoolConfig<false>,
1619}
1620
1621#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1622#[serde(deny_unknown_fields, rename_all = "camelCase")]
1623pub struct ConstModulesConfig {
1624 #[serde(default)]
1625 pub globals: FxHashMap<Atom, FxHashMap<Atom, BytesStr>>,
1626}
1627
1628#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1629#[serde(deny_unknown_fields, rename_all = "camelCase")]
1630pub struct OptimizerConfig {
1631 #[serde(default)]
1632 pub globals: Option<GlobalPassOption>,
1633
1634 #[serde(default)]
1635 pub simplify: Option<SimplifyOption>,
1636
1637 #[serde(default)]
1638 pub jsonify: Option<JsonifyOption>,
1639}
1640
1641#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
1642#[serde(untagged)]
1643pub enum SimplifyOption {
1644 Bool(bool),
1645 Json(SimplifyJsonOption),
1646}
1647
1648impl Default for SimplifyOption {
1649 fn default() -> Self {
1650 SimplifyOption::Bool(true)
1651 }
1652}
1653
1654#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
1655#[serde(deny_unknown_fields, rename_all = "camelCase")]
1656pub struct SimplifyJsonOption {
1657 #[serde(default = "default_preserve_imports_with_side_effects")]
1658 pub preserve_imports_with_side_effects: bool,
1659}
1660
1661fn default_preserve_imports_with_side_effects() -> bool {
1662 true
1663}
1664
1665#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
1666#[serde(deny_unknown_fields, rename_all = "camelCase")]
1667pub struct JsonifyOption {
1668 #[serde(default = "default_jsonify_min_cost")]
1669 pub min_cost: usize,
1670}
1671
1672fn default_jsonify_min_cost() -> usize {
1673 1024
1674}
1675
1676#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Merge)]
1677#[serde(deny_unknown_fields, rename_all = "camelCase")]
1678pub struct ErrorConfig {
1679 pub filename: BoolConfig<true>,
1680}
1681
1682#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1683#[serde(deny_unknown_fields, rename_all = "camelCase")]
1684pub struct GlobalPassOption {
1685 #[serde(default)]
1686 pub vars: IndexMap<Atom, Atom, FxBuildHasher>,
1687 #[serde(default)]
1688 pub envs: GlobalInliningPassEnvs,
1689
1690 #[serde(default)]
1691 pub typeofs: FxHashMap<Atom, Atom>,
1692}
1693
1694#[derive(Debug, Clone, Serialize, Deserialize)]
1695#[serde(untagged)]
1696pub enum GlobalInliningPassEnvs {
1697 List(FxHashSet<String>),
1698 Map(FxHashMap<Atom, Atom>),
1699}
1700
1701impl Default for GlobalInliningPassEnvs {
1702 fn default() -> Self {
1703 GlobalInliningPassEnvs::List(Default::default())
1704 }
1705}
1706
1707impl GlobalPassOption {
1708 pub fn build(
1709 self,
1710 cm: &SourceMap,
1711 handler: &Handler,
1712 unresolved_mark: Mark,
1713 ) -> impl 'static + Pass {
1714 type ValuesMap = Arc<FxHashMap<Atom, Expr>>;
1715
1716 fn expr(cm: &SourceMap, handler: &Handler, src: String) -> Box<Expr> {
1717 let fm = cm.new_source_file(FileName::Anon.into(), src);
1718
1719 let mut errors = Vec::new();
1720 let expr = parse_file_as_expr(
1721 &fm,
1722 Syntax::Es(Default::default()),
1723 Default::default(),
1724 None,
1725 &mut errors,
1726 );
1727
1728 for e in errors {
1729 e.into_diagnostic(handler).emit()
1730 }
1731
1732 match expr {
1733 Ok(v) => v,
1734 _ => panic!("{} is not a valid expression", fm.src),
1735 }
1736 }
1737
1738 fn mk_map(
1739 cm: &SourceMap,
1740 handler: &Handler,
1741 values: impl Iterator<Item = (Atom, Atom)>,
1742 is_env: bool,
1743 ) -> ValuesMap {
1744 let mut m = HashMap::default();
1745
1746 for (k, v) in values {
1747 let v = if is_env {
1748 format!("'{v}'")
1749 } else {
1750 (*v).into()
1751 };
1752 let v_str = v.clone();
1753
1754 let e = expr(cm, handler, v_str);
1755
1756 m.insert((*k).into(), *e);
1757 }
1758
1759 Arc::new(m)
1760 }
1761
1762 let env_map = if cfg!(target_arch = "wasm32") {
1763 Arc::new(Default::default())
1764 } else {
1765 match &self.envs {
1766 GlobalInliningPassEnvs::List(env_list) => {
1767 static CACHE: Lazy<DashMap<Vec<String>, ValuesMap, FxBuildHasher>> =
1768 Lazy::new(Default::default);
1769
1770 let cache_key = env_list.iter().cloned().collect::<Vec<_>>();
1771 if let Some(v) = CACHE.get(&cache_key).as_deref().cloned() {
1772 v
1773 } else {
1774 let map = mk_map(
1775 cm,
1776 handler,
1777 env::vars()
1778 .filter(|(k, _)| env_list.contains(k))
1779 .map(|(k, v)| (k.into(), v.into())),
1780 true,
1781 );
1782 CACHE.insert(cache_key, map.clone());
1783 map
1784 }
1785 }
1786
1787 GlobalInliningPassEnvs::Map(map) => {
1788 static CACHE: Lazy<DashMap<Vec<(Atom, Atom)>, ValuesMap, FxBuildHasher>> =
1789 Lazy::new(Default::default);
1790
1791 let cache_key = self
1792 .vars
1793 .iter()
1794 .map(|(k, v)| (k.clone(), v.clone()))
1795 .collect::<Vec<_>>();
1796 if let Some(v) = CACHE.get(&cache_key) {
1797 (*v).clone()
1798 } else {
1799 let map = mk_map(
1800 cm,
1801 handler,
1802 map.iter().map(|(k, v)| (k.clone(), v.clone())),
1803 false,
1804 );
1805 CACHE.insert(cache_key, map.clone());
1806 map
1807 }
1808 }
1809 }
1810 };
1811
1812 let global_exprs = {
1813 static CACHE: Lazy<DashMap<Vec<(Atom, Atom)>, GlobalExprMap, FxBuildHasher>> =
1814 Lazy::new(Default::default);
1815
1816 let cache_key = self
1817 .vars
1818 .iter()
1819 .filter(|(k, _)| k.contains('.'))
1820 .map(|(k, v)| (k.clone(), v.clone()))
1821 .collect::<Vec<_>>();
1822
1823 if let Some(v) = CACHE.get(&cache_key) {
1824 (*v).clone()
1825 } else {
1826 let map = self
1827 .vars
1828 .iter()
1829 .filter(|(k, _)| k.contains('.'))
1830 .map(|(k, v)| {
1831 (
1832 NodeIgnoringSpan::owned(*expr(cm, handler, k.to_string())),
1833 *expr(cm, handler, v.to_string()),
1834 )
1835 })
1836 .collect::<FxHashMap<_, _>>();
1837 let map = Arc::new(map);
1838 CACHE.insert(cache_key, map.clone());
1839 map
1840 }
1841 };
1842
1843 let global_map = {
1844 static CACHE: Lazy<DashMap<Vec<(Atom, Atom)>, ValuesMap, FxBuildHasher>> =
1845 Lazy::new(Default::default);
1846
1847 let cache_key = self
1848 .vars
1849 .iter()
1850 .filter(|(k, _)| !k.contains('.'))
1851 .map(|(k, v)| (k.clone(), v.clone()))
1852 .collect::<Vec<_>>();
1853 if let Some(v) = CACHE.get(&cache_key) {
1854 (*v).clone()
1855 } else {
1856 let map = mk_map(
1857 cm,
1858 handler,
1859 self.vars.into_iter().filter(|(k, _)| !k.contains('.')),
1860 false,
1861 );
1862 CACHE.insert(cache_key, map.clone());
1863 map
1864 }
1865 };
1866
1867 inline_globals(
1868 unresolved_mark,
1869 env_map,
1870 global_map,
1871 global_exprs,
1872 Arc::new(self.typeofs),
1873 )
1874 }
1875}
1876
1877pub(crate) fn default_env_name() -> String {
1878 if let Ok(v) = env::var("SWC_ENV") {
1879 return v;
1880 }
1881
1882 match env::var("NODE_ENV") {
1883 Ok(v) => v,
1884 Err(_) => "development".into(),
1885 }
1886}
1887
1888fn build_resolver(
1889 mut base_url: PathBuf,
1890 paths: CompiledPaths,
1891 resolve_fully: bool,
1892 file_extension: &str,
1893) -> SwcImportResolver {
1894 static CACHE: Lazy<
1895 DashMap<(PathBuf, CompiledPaths, bool, String), SwcImportResolver, FxBuildHasher>,
1896 > = Lazy::new(Default::default);
1897
1898 if cfg!(target_os = "windows") {
1900 base_url = base_url
1901 .canonicalize()
1902 .with_context(|| {
1903 format!(
1904 "failed to canonicalize jsc.baseUrl(`{}`)\nThis is required on Windows \
1905 because of UNC path.",
1906 base_url.display()
1907 )
1908 })
1909 .unwrap();
1910 }
1911
1912 if let Some(cached) = CACHE.get(&(
1913 base_url.clone(),
1914 paths.clone(),
1915 resolve_fully,
1916 file_extension.to_owned(),
1917 )) {
1918 return cached.clone();
1919 }
1920
1921 let r = {
1922 let r = NodeModulesResolver::without_node_modules(
1923 swc_ecma_loader::TargetEnv::Node,
1924 Default::default(),
1925 true,
1926 );
1927
1928 let r = CachingResolver::new(1024, r);
1929
1930 let r = TsConfigResolver::new(r, base_url.clone(), paths.clone());
1931 let r = CachingResolver::new(256, r);
1932
1933 let r = NodeImportResolver::with_config(
1934 r,
1935 swc_ecma_transforms::modules::path::Config {
1936 base_dir: Some(base_url.clone()),
1937 resolve_fully,
1938 file_extension: file_extension.to_owned(),
1939 },
1940 );
1941 Arc::new(r)
1942 };
1943
1944 CACHE.insert(
1945 (base_url, paths, resolve_fully, file_extension.to_owned()),
1946 r.clone(),
1947 );
1948
1949 r
1950}