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, 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")]
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            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        // Do a resolver pass before everything.
334        //
335        // We do this before creating custom passes, so custom passses can use the
336        // variable management system based on the syntax contexts.
337        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            // TODO: should Preserve for SystemJS
557            _ => 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        // inline_script defaults to true, but it's case only if minify is enabled.
572        // This is because minifier API is compatible with Terser, and Terser
573        // defaults to true, while by default swc itself doesn't enable
574        // inline_script by default.
575        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        // Implementing finalize logic directly
603        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        // compat
622        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            // module / helper
654            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            // Embedded runtime plugin target, based on assumption we have
704            // 1. filesystem access for the cache
705            // 2. embedded runtime can compiles & execute wasm
706            #[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            // Native runtime plugin target, based on assumption we have
735            // 1. no filesystem access, loading binary / cache management should be
736            // performed externally
737            // 2. native runtime compiles & execute wasm (i.e v8 on node, chrome)
738            #[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                    // Decorators may use type information
811                    Optional::new(decorator_pass, syntax.decorators()),
812                    Optional::new(
813                        explicit_resource_management(),
814                        syntax.explicit_resource_management(),
815                    ),
816                ),
817                // The transform strips import assertions, so it's only enabled if
818                // keep_import_assertions is false.
819                (
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                    // handle jsx
874                    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/// `.swcrc` file
970#[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    /// This method returns `Ok(None)` if the file should be ignored.
1039    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                // TODO
1052                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/// A single object in the `.swcrc` file
1075#[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    /// Possible values are: `'inline'`, `true`, `false`.
1100    #[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    /// Adjust config for `file`.
1124    ///
1125    ///
1126    ///
1127    /// - typescript: `tsx` will be modified if file extension is `ts`.
1128    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                //
1179                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/// One `BuiltConfig` per a directory with swcrc
1211#[non_exhaustive]
1212pub struct BuiltInput<P: Pass> {
1213    pub program: Program,
1214    pub pass: P,
1215    pub syntax: Syntax,
1216    pub target: EsVersion,
1217    /// Minification for **codegen**. Minifier transforms will be inserted into
1218    /// `pass`.
1219    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/// `jsc` in  `.swcrc`.
1282#[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    /// https://www.typescriptlang.org/tsconfig/#rewriteRelativeImportExtensions
1329    #[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/// `jsc.experimental` in `.swcrc`
1360#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
1361#[serde(deny_unknown_fields, rename_all = "camelCase")]
1362pub struct JscExperimental {
1363    /// This requires cargo feature `plugin`.
1364    #[serde(default)]
1365    pub plugins: Option<Vec<PluginConfig>>,
1366    #[serde(default)]
1367    pub plugin_env_vars: Option<Vec<Atom>>,
1368    /// If true, keeps import assertions in the output.
1369    #[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    /// Location where swc may stores its intermediate cache.
1375    /// Currently this is only being used for wasm plugin's bytecache.
1376    /// Path should be absolute directory, which will be created if not exist.
1377    /// This configuration behavior can change anytime under experimental flag
1378    /// and will not be considered as breaking changes.
1379    #[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    /// Emit TypeScript definitions for `.ts`, `.tsx` files.
1389    ///
1390    /// This requires `isolatedDeclartion` feature of TypeScript 5.5.
1391    #[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
1438/// `paths` section of `tsconfig.json`.
1439pub 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    /// https://www.typescriptlang.org/tsconfig#useDefineForClassFields
1616    #[serde(default)]
1617    pub use_define_for_class_fields: BoolConfig<true>,
1618
1619    /// https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax
1620    #[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    // On Windows, we need to normalize path as UNC path.
1904    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}