swc/config/
mod.rs

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")]
86/// A shared instance to plugin's module bytecode cache.
87pub static PLUGIN_MODULE_CACHE: Lazy<swc_plugin_runner::cache::PluginModuleCache> =
88    Lazy::new(Default::default);
89
90/// Create a new cache instance if not initialized. This can be called multiple
91/// time, but any subsequent call will be ignored.
92///
93/// This fn have a side effect to create path to cache if given path is not
94/// resolvable if fs_cache_store is enabled. If root is not specified, it'll
95/// generate default root for cache location.
96///
97/// If cache failed to initialize filesystem cache for given location
98/// it'll be serve in-memory cache only.
99#[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    /// `parse`: `(syntax, target, is_module)`
253    ///
254    /// `parse` should use `comments`.
255    #[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        // Do a resolver pass before everything.
333        //
334        // We do this before creating custom passes, so custom passses can use the
335        // variable management system based on the syntax contexts.
336        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            // TODO: should Preserve for SystemJS
556            _ => 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        // inline_script defaults to true, but it's case only if minify is enabled.
571        // This is because minifier API is compatible with Terser, and Terser
572        // defaults to true, while by default swc itself doesn't enable
573        // inline_script by default.
574        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        // Implementing finalize logic directly
602        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        // compat
621        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            // module / helper
653            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            // Embedded runtime plugin target, based on assumption we have
702            // 1. filesystem access for the cache
703            // 2. embedded runtime can compiles & execute wasm
704            #[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            // Native runtime plugin target, based on assumption we have
733            // 1. no filesystem access, loading binary / cache management should be
734            // performed externally
735            // 2. native runtime compiles & execute wasm (i.e v8 on node, chrome)
736            #[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                    // Decorators may use type information
806                    Optional::new(decorator_pass, syntax.decorators()),
807                    Optional::new(
808                        explicit_resource_management(),
809                        syntax.explicit_resource_management(),
810                    ),
811                ),
812                // The transform strips import assertions, so it's only enabled if
813                // keep_import_assertions is false.
814                (
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                    // handle jsx
869                    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/// `.swcrc` file
965#[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    /// This method returns `Ok(None)` if the file should be ignored.
1034    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                // TODO
1047                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/// A single object in the `.swcrc` file
1070#[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    /// Possible values are: `'inline'`, `true`, `false`.
1095    #[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    /// Adjust config for `file`.
1119    ///
1120    ///
1121    ///
1122    /// - typescript: `tsx` will be modified if file extension is `ts`.
1123    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                //
1174                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/// One `BuiltConfig` per a directory with swcrc
1206#[non_exhaustive]
1207pub struct BuiltInput<P: Pass> {
1208    pub program: Program,
1209    pub pass: P,
1210    pub syntax: Syntax,
1211    pub target: EsVersion,
1212    /// Minification for **codegen**. Minifier transforms will be inserted into
1213    /// `pass`.
1214    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/// `jsc` in  `.swcrc`.
1277#[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/// `jsc.experimental` in `.swcrc`
1351#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1352#[serde(deny_unknown_fields, rename_all = "camelCase")]
1353pub struct JscExperimental {
1354    /// This requires cargo feature `plugin`.
1355    #[serde(default)]
1356    pub plugins: Option<Vec<PluginConfig>>,
1357    #[serde(default)]
1358    pub plugin_env_vars: Option<Vec<Atom>>,
1359    /// If true, keeps import assertions in the output.
1360    #[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    /// Location where swc may stores its intermediate cache.
1366    /// Currently this is only being used for wasm plugin's bytecache.
1367    /// Path should be absolute directory, which will be created if not exist.
1368    /// This configuration behavior can change anytime under experimental flag
1369    /// and will not be considered as breaking changes.
1370    #[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    /// Emit TypeScript definitions for `.ts`, `.tsx` files.
1380    ///
1381    /// This requires `isolatedDeclartion` feature of TypeScript 5.5.
1382    #[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
1429/// `paths` section of `tsconfig.json`.
1430pub 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    /// https://www.typescriptlang.org/tsconfig#useDefineForClassFields
1600    #[serde(default)]
1601    pub use_define_for_class_fields: BoolConfig<true>,
1602
1603    /// https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax
1604    #[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    // On Windows, we need to normalize path as UNC path.
1899    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}