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