swc/
lib.rs

1//! The main crate of the swc project.
2//!
3//!
4//!
5//! # Customizing
6//!
7//!
8//! This is documentation for building custom build tools on top of swc.
9//!
10//! ## Dependency version management
11//!
12//! `swc` has [swc_css](https://docs.rs/swc_css), which re-exports required modules.
13//!
14//! ## Testing
15//!
16//! See [testing] and [swc_ecma_transforms_testing](https://docs.rs/swc_ecma_transforms_testing).
17//!
18//! ## Custom javascript transforms
19//!
20//!
21//!
22//! ### What is [Atom](swc_atoms::Atom)?
23//!
24//! It's basically an interned string. See [swc_atoms].
25//!
26//! ### Choosing between [Atom](swc_atoms::Atom) vs String
27//!
28//! You should  prefer [Atom](swc_atoms::Atom) over [String] if it's going
29//! to be stored in an AST node.
30//!
31//! See [swc_atoms] for detailed description.
32//!
33//! ### Fold vs VisitMut vs Visit
34//!
35//! See [swc_visit] for detailed description.
36//!
37//!
38//!  - [Fold](swc_ecma_visit::Fold)
39//!  - [VisitMut](swc_ecma_visit::VisitMut)
40//!  - [Visit](swc_ecma_visit::Visit)
41//!
42//!
43//! ### Variable management (Scoping)
44//!
45//! See [swc_ecma_transforms_base::resolver::resolver_with_mark].
46//!
47//! #### How identifiers work
48//!
49//! See the doc on [swc_ecma_ast::Ident] or on
50//! [swc_ecma_transforms_base::resolver::resolver_with_mark].
51//!
52//! #### Comparing two identifiers
53//!
54//! See [swc_ecma_utils::Id]. You can use [swc_ecma_utils::IdentLike::to_id] to
55//! extract important parts of an [swc_ecma_ast::Ident].
56//!
57//! #### Creating a unique identifier
58//!
59//! See [swc_ecma_utils::private_ident].
60//!
61//! #### Prepending statements
62//!
63//! If you want to prepend statements to the beginning of a file, you can use
64//! [swc_ecma_utils::prepend_stmts] or [swc_ecma_utils::prepend] if `len == 1`.
65//!
66//! These methods are aware of the fact that `"use strict"` directive should be
67//! first in a file, and insert statements after directives.
68//!
69//! ### Improving readability
70//!
71//! Each stuffs are documented at itself.
72//!
73//!  - If you are creating or binding an [swc_ecma_ast::Expr] with operator, you
74//!    can use [swc_ecma_ast::op].
75//!
76//!  - If you want to create [swc_ecma_ast::CallExpr], you can use
77//!    [swc_ecma_utils::ExprFactory::as_callee] to create `callee`.
78//!
79//!  - If you want to create [swc_ecma_ast::CallExpr] or
80//!    [swc_ecma_ast::NewExpr], you can use
81//!    [swc_ecma_utils::ExprFactory::as_arg] to create arguments.
82//!
83//!
84//!  - If you want to create [swc_ecma_ast::MemberExpr] where all identifiers
85//!    are static (e.g. `Object.prototype.hasOwnProperty`), you can use
86//!    [swc_ecma_utils::member_expr].
87//!
88//!  - If you want to create [swc_ecma_ast::MemberExpr], you can use
89//!    [swc_ecma_utils::ExprFactory::as_obj] to create object field.
90//!
91//!
92//! ### Reducing binary size
93//!
94//! The visitor expands to a lot of code. You can reduce it by using macros like
95//!
96//!  - [noop_fold_type](swc_ecma_visit::noop_fold_type)
97//!  - [noop_visit_mut_type](swc_ecma_visit::noop_visit_mut_type)
98//!  - [noop_visit_type](swc_ecma_visit::noop_visit_type)
99//!
100//! Note that this will make typescript-related nodes not processed, but it's
101//! typically fine as `typescript::strip` is invoked at the start and it removes
102//! typescript-specific nodes.
103//!
104//! ### Porting `expr.evaluate()` of babel
105//!
106//! See [swc_ecma_minifier::eval::Evaluator].
107#![deny(unused)]
108#![allow(clippy::too_many_arguments)]
109#![allow(clippy::mutable_key_type)]
110#![cfg_attr(docsrs, feature(doc_cfg))]
111
112pub extern crate swc_atoms as atoms;
113extern crate swc_common as common;
114
115use std::{
116    cell::RefCell,
117    fs::{read_to_string, File},
118    io::ErrorKind,
119    path::{Path, PathBuf},
120    sync::Arc,
121};
122
123use anyhow::{bail, Context, Error};
124use base64::prelude::{Engine, BASE64_STANDARD};
125use common::{
126    comments::{Comment, SingleThreadedComments},
127    errors::HANDLER,
128};
129use jsonc_parser::{parse_to_serde_value, ParseOptions};
130use once_cell::sync::Lazy;
131use serde_json::error::Category;
132pub use sourcemap;
133use swc_common::{
134    comments::Comments, errors::Handler, sync::Lrc, FileName, Mark, SourceFile, SourceMap, Spanned,
135    GLOBALS,
136};
137pub use swc_compiler_base::{PrintArgs, TransformOutput};
138pub use swc_config::config_types::{BoolConfig, BoolOr, BoolOrDataConfig};
139use swc_ecma_ast::{noop_pass, EsVersion, Pass, Program};
140use swc_ecma_codegen::{to_code_with_comments, Node};
141use swc_ecma_loader::resolvers::{
142    lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
143};
144use swc_ecma_minifier::option::{MangleCache, MinifyOptions, TopLevelOptions};
145use swc_ecma_parser::{EsSyntax, Syntax};
146use swc_ecma_transforms::{
147    fixer,
148    helpers::{self, Helpers},
149    hygiene,
150    modules::{path::NodeImportResolver, rewriter::import_rewriter},
151    resolver,
152};
153use swc_ecma_transforms_base::fixer::paren_remover;
154use swc_ecma_visit::{FoldWith, VisitMutWith, VisitWith};
155pub use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
156pub use swc_node_comments::SwcComments;
157use swc_timer::timer;
158use swc_transform_common::output::emit;
159use swc_typescript::fast_dts::FastDts;
160use tracing::warn;
161use url::Url;
162
163pub use crate::builder::PassBuilder;
164use crate::config::{
165    BuiltInput, Config, ConfigFile, InputSourceMap, IsModule, JsMinifyCommentOption,
166    JsMinifyOptions, Options, OutputCharset, Rc, RootMode, SourceMapsConfig,
167};
168
169mod builder;
170pub mod config;
171mod dropped_comments_preserver;
172mod plugin;
173pub mod resolver {
174    use std::path::PathBuf;
175
176    use rustc_hash::FxHashMap;
177    use swc_ecma_loader::{
178        resolvers::{lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver},
179        TargetEnv,
180    };
181
182    use crate::config::CompiledPaths;
183
184    pub type NodeResolver = CachingResolver<NodeModulesResolver>;
185
186    pub fn paths_resolver(
187        target_env: TargetEnv,
188        alias: FxHashMap<String, String>,
189        base_url: PathBuf,
190        paths: CompiledPaths,
191        preserve_symlinks: bool,
192    ) -> CachingResolver<TsConfigResolver<NodeModulesResolver>> {
193        let r = TsConfigResolver::new(
194            NodeModulesResolver::without_node_modules(target_env, alias, preserve_symlinks),
195            base_url,
196            paths,
197        );
198        CachingResolver::new(40, r)
199    }
200
201    pub fn environment_resolver(
202        target_env: TargetEnv,
203        alias: FxHashMap<String, String>,
204        preserve_symlinks: bool,
205    ) -> NodeResolver {
206        CachingResolver::new(
207            40,
208            NodeModulesResolver::new(target_env, alias, preserve_symlinks),
209        )
210    }
211}
212
213type SwcImportResolver = Arc<
214    NodeImportResolver<CachingResolver<TsConfigResolver<CachingResolver<NodeModulesResolver>>>>,
215>;
216
217/// All methods accept [Handler], which is a storage for errors.
218///
219/// The caller should check if the handler contains any errors after calling
220/// method.
221pub struct Compiler {
222    /// CodeMap
223    pub cm: Arc<SourceMap>,
224    comments: SwcComments,
225}
226
227/// These are **low-level** apis.
228impl Compiler {
229    pub fn comments(&self) -> &SwcComments {
230        &self.comments
231    }
232
233    /// Runs `op` in current compiler's context.
234    ///
235    /// Note: Other methods of `Compiler` already uses this internally.
236    pub fn run<R, F>(&self, op: F) -> R
237    where
238        F: FnOnce() -> R,
239    {
240        debug_assert!(
241            GLOBALS.is_set(),
242            "`swc_common::GLOBALS` is required for this operation"
243        );
244
245        op()
246    }
247
248    fn get_orig_src_map(
249        &self,
250        fm: &SourceFile,
251        input_src_map: &InputSourceMap,
252        comments: &[Comment],
253        is_default: bool,
254    ) -> Result<Option<sourcemap::SourceMap>, Error> {
255        self.run(|| -> Result<_, Error> {
256            let name = &fm.name;
257
258            let read_inline_sourcemap =
259                |data_url: &str| -> Result<Option<sourcemap::SourceMap>, Error> {
260                    let url = Url::parse(data_url).with_context(|| {
261                        format!("failed to parse inline source map url\n{}", data_url)
262                    })?;
263
264                    let idx = match url.path().find("base64,") {
265                        Some(v) => v,
266                        None => {
267                            bail!("failed to parse inline source map: not base64: {:?}", url)
268                        }
269                    };
270
271                    let content = url.path()[idx + "base64,".len()..].trim();
272
273                    let res = BASE64_STANDARD
274                        .decode(content.as_bytes())
275                        .context("failed to decode base64-encoded source map")?;
276
277                    Ok(Some(sourcemap::SourceMap::from_slice(&res).context(
278                        "failed to read input source map from inlined base64 encoded string",
279                    )?))
280                };
281
282            let read_file_sourcemap =
283                |data_url: Option<&str>| -> Result<Option<sourcemap::SourceMap>, Error> {
284                    match &**name {
285                        FileName::Real(filename) => {
286                            let dir = match filename.parent() {
287                                Some(v) => v,
288                                None => {
289                                    bail!("unexpected: root directory is given as a input file")
290                                }
291                            };
292
293                            let map_path = match data_url {
294                                Some(data_url) => {
295                                    let mut map_path = dir.join(data_url);
296                                    if !map_path.exists() {
297                                        // Old behavior. This check would prevent
298                                        // regressions.
299                                        // Perhaps it shouldn't be supported. Sometimes
300                                        // developers don't want to expose their source
301                                        // code.
302                                        // Map files are for internal troubleshooting
303                                        // convenience.
304                                        let fallback_map_path =
305                                            PathBuf::from(format!("{}.map", filename.display()));
306                                        if fallback_map_path.exists() {
307                                            map_path = fallback_map_path;
308                                        } else {
309                                            bail!(
310                                                "failed to find input source map file {:?} in \
311                                                 {:?} file as either {:?} or with appended .map",
312                                                data_url,
313                                                filename.display(),
314                                                map_path.display(),
315                                            )
316                                        }
317                                    }
318
319                                    Some(map_path)
320                                }
321                                None => {
322                                    // Old behavior.
323                                    let map_path =
324                                        PathBuf::from(format!("{}.map", filename.display()));
325                                    if map_path.exists() {
326                                        Some(map_path)
327                                    } else {
328                                        None
329                                    }
330                                }
331                            };
332
333                            match map_path {
334                                Some(map_path) => {
335                                    let path = map_path.display().to_string();
336                                    let file = File::open(&path);
337
338                                    // If file is not found, we should return None.
339                                    // Some libraries generates source map but omit them from the
340                                    // npm package.
341                                    //
342                                    // See https://github.com/swc-project/swc/issues/8789#issuecomment-2105055772
343                                    if file
344                                        .as_ref()
345                                        .is_err_and(|err| err.kind() == ErrorKind::NotFound)
346                                    {
347                                        warn!(
348                                            "source map is specified by sourceMappingURL but \
349                                             there's no source map at `{}`",
350                                            path
351                                        );
352                                        return Ok(None);
353                                    }
354
355                                    // Old behavior.
356                                    let file = if !is_default {
357                                        file?
358                                    } else {
359                                        match file {
360                                            Ok(v) => v,
361                                            Err(_) => return Ok(None),
362                                        }
363                                    };
364
365                                    Ok(Some(sourcemap::SourceMap::from_reader(file).with_context(
366                                        || {
367                                            format!(
368                                                "failed to read input source map
369                                from file at {}",
370                                                path
371                                            )
372                                        },
373                                    )?))
374                                }
375                                None => Ok(None),
376                            }
377                        }
378                        _ => Ok(None),
379                    }
380                };
381
382            let read_sourcemap = || -> Option<sourcemap::SourceMap> {
383                let s = "sourceMappingURL=";
384
385                let text = comments.iter().rev().find_map(|c| {
386                    let idx = c.text.rfind(s)?;
387                    let (_, url) = c.text.split_at(idx + s.len());
388
389                    Some(url.trim())
390                });
391
392                // Load original source map if possible
393                let result = match text {
394                    Some(text) if text.starts_with("data:") => read_inline_sourcemap(text),
395                    _ => read_file_sourcemap(text),
396                };
397                match result {
398                    Ok(r) => r,
399                    Err(err) => {
400                        tracing::error!("failed to read input source map: {:?}", err);
401                        None
402                    }
403                }
404            };
405
406            // Load original source map
407            match input_src_map {
408                InputSourceMap::Bool(false) => Ok(None),
409                InputSourceMap::Bool(true) => Ok(read_sourcemap()),
410                InputSourceMap::Str(ref s) => {
411                    if s == "inline" {
412                        Ok(read_sourcemap())
413                    } else {
414                        // Load source map passed by user
415                        Ok(Some(
416                            sourcemap::SourceMap::from_slice(s.as_bytes()).context(
417                                "failed to read input source map from user-provided sourcemap",
418                            )?,
419                        ))
420                    }
421                }
422            }
423        })
424    }
425
426    /// This method parses a javascript / typescript file
427    pub fn parse_js(
428        &self,
429        fm: Arc<SourceFile>,
430        handler: &Handler,
431        target: EsVersion,
432        syntax: Syntax,
433        is_module: IsModule,
434        comments: Option<&dyn Comments>,
435    ) -> Result<Program, Error> {
436        swc_compiler_base::parse_js(
437            self.cm.clone(),
438            fm,
439            handler,
440            target,
441            syntax,
442            is_module,
443            comments,
444        )
445    }
446
447    /// Converts ast node to source string and sourcemap.
448    ///
449    ///
450    /// This method receives target file path, but does not write file to the
451    /// path. See: https://github.com/swc-project/swc/issues/1255
452    #[allow(clippy::too_many_arguments)]
453    pub fn print<T>(&self, node: &T, args: PrintArgs) -> Result<TransformOutput, Error>
454    where
455        T: Node + VisitWith<swc_compiler_base::IdentCollector>,
456    {
457        swc_compiler_base::print(self.cm.clone(), node, args)
458    }
459}
460
461/// High-level apis.
462impl Compiler {
463    pub fn new(cm: Arc<SourceMap>) -> Self {
464        Compiler {
465            cm,
466            comments: Default::default(),
467        }
468    }
469
470    #[tracing::instrument(skip_all)]
471    pub fn read_config(&self, opts: &Options, name: &FileName) -> Result<Option<Config>, Error> {
472        static CUR_DIR: Lazy<PathBuf> = Lazy::new(|| {
473            if cfg!(target_arch = "wasm32") {
474                PathBuf::new()
475            } else {
476                ::std::env::current_dir().unwrap()
477            }
478        });
479
480        self.run(|| -> Result<_, Error> {
481            let Options {
482                ref root,
483                root_mode,
484                swcrc,
485                config_file,
486                ..
487            } = opts;
488
489            let root = root.as_ref().unwrap_or(&CUR_DIR);
490
491            let swcrc_path = match config_file {
492                Some(ConfigFile::Str(s)) => Some(PathBuf::from(s.clone())),
493                _ => {
494                    if *swcrc {
495                        if let FileName::Real(ref path) = name {
496                            find_swcrc(path, root, *root_mode)
497                        } else {
498                            None
499                        }
500                    } else {
501                        None
502                    }
503                }
504            };
505
506            let config_file = match swcrc_path.as_deref() {
507                Some(s) => Some(load_swcrc(s)?),
508                _ => None,
509            };
510            let filename_path = match name {
511                FileName::Real(p) => Some(&**p),
512                _ => None,
513            };
514
515            if let Some(filename_path) = filename_path {
516                if let Some(config) = config_file {
517                    let dir = swcrc_path
518                        .as_deref()
519                        .and_then(|p| p.parent())
520                        .expect(".swcrc path should have parent dir");
521
522                    let mut config = config
523                        .into_config(Some(filename_path))
524                        .context("failed to process config file")?;
525
526                    if let Some(c) = &mut config {
527                        if c.jsc.base_url != PathBuf::new() {
528                            let joined = dir.join(&c.jsc.base_url);
529                            c.jsc.base_url = if cfg!(target_os = "windows")
530                                && c.jsc.base_url.as_os_str() == "."
531                            {
532                                dir.canonicalize().with_context(|| {
533                                    format!(
534                                        "failed to canonicalize base url using the path of \
535                                         .swcrc\nDir: {}\n(Used logic for windows)",
536                                        dir.display(),
537                                    )
538                                })?
539                            } else {
540                                joined.canonicalize().with_context(|| {
541                                    format!(
542                                        "failed to canonicalize base url using the path of \
543                                         .swcrc\nPath: {}\nDir: {}\nbaseUrl: {}",
544                                        joined.display(),
545                                        dir.display(),
546                                        c.jsc.base_url.display()
547                                    )
548                                })?
549                            };
550                        }
551                    }
552
553                    return Ok(config);
554                }
555
556                let config_file = config_file.unwrap_or_default();
557                let config = config_file.into_config(Some(filename_path))?;
558
559                return Ok(config);
560            }
561
562            let config = match config_file {
563                Some(config_file) => config_file.into_config(None)?,
564                None => Rc::default().into_config(None)?,
565            };
566
567            match config {
568                Some(config) => Ok(Some(config)),
569                None => {
570                    bail!("no config matched for file ({})", name)
571                }
572            }
573        })
574        .with_context(|| format!("failed to read .swcrc file for input file at `{}`", name))
575    }
576
577    /// This method returns [None] if a file should be skipped.
578    ///
579    /// This method handles merging of config.
580    ///
581    /// This method does **not** parse module.
582    #[tracing::instrument(skip_all)]
583    pub fn parse_js_as_input<'a, P>(
584        &'a self,
585        fm: Lrc<SourceFile>,
586        program: Option<Program>,
587        handler: &'a Handler,
588        opts: &Options,
589        name: &FileName,
590        comments: Option<&'a SingleThreadedComments>,
591        before_pass: impl 'a + FnOnce(&Program) -> P,
592    ) -> Result<Option<BuiltInput<impl 'a + Pass>>, Error>
593    where
594        P: 'a + Pass,
595    {
596        self.run(move || {
597            let _timer = timer!("Compiler.parse");
598
599            if let FileName::Real(ref path) = name {
600                if !opts.config.matches(path)? {
601                    return Ok(None);
602                }
603            }
604
605            let config = self.read_config(opts, name)?;
606            let config = match config {
607                Some(v) => v,
608                None => return Ok(None),
609            };
610
611            let built = opts.build_as_input(
612                &self.cm,
613                name,
614                move |syntax, target, is_module| match program {
615                    Some(v) => Ok(v),
616                    _ => self.parse_js(
617                        fm.clone(),
618                        handler,
619                        target,
620                        syntax,
621                        is_module,
622                        comments.as_ref().map(|v| v as _),
623                    ),
624                },
625                opts.output_path.as_deref(),
626                opts.source_root.clone(),
627                opts.source_file_name.clone(),
628                handler,
629                Some(config),
630                comments,
631                before_pass,
632            )?;
633            Ok(Some(built))
634        })
635    }
636
637    pub fn run_transform<F, Ret>(&self, handler: &Handler, external_helpers: bool, op: F) -> Ret
638    where
639        F: FnOnce() -> Ret,
640    {
641        self.run(|| {
642            helpers::HELPERS.set(&Helpers::new(external_helpers), || HANDLER.set(handler, op))
643        })
644    }
645
646    #[tracing::instrument(skip_all)]
647    pub fn transform(
648        &self,
649        handler: &Handler,
650        program: Program,
651        external_helpers: bool,
652        mut pass: impl swc_ecma_visit::Fold,
653    ) -> Program {
654        self.run_transform(handler, external_helpers, || {
655            // Fold module
656            program.fold_with(&mut pass)
657        })
658    }
659
660    /// `custom_after_pass` is applied after swc transforms are applied.
661    ///
662    /// `program`: If you already parsed `Program`, you can pass it.
663    ///
664    /// # Guarantee
665    ///
666    /// `swc` invokes `custom_before_pass` after
667    ///
668    ///  - Handling decorators, if configured
669    ///  - Applying `resolver`
670    ///  - Stripping typescript nodes
671    ///
672    /// This means, you can use `noop_visit_type`, `noop_fold_type` and
673    /// `noop_visit_mut_type` in your visitor to reduce the binary size.
674    #[tracing::instrument(skip_all)]
675    pub fn process_js_with_custom_pass<P1, P2>(
676        &self,
677        fm: Arc<SourceFile>,
678        program: Option<Program>,
679        handler: &Handler,
680        opts: &Options,
681        comments: SingleThreadedComments,
682        custom_before_pass: impl FnOnce(&Program) -> P1,
683        custom_after_pass: impl FnOnce(&Program) -> P2,
684    ) -> Result<TransformOutput, Error>
685    where
686        P1: Pass,
687        P2: Pass,
688    {
689        self.run(|| -> Result<_, Error> {
690            let config = self.run(|| {
691                self.parse_js_as_input(
692                    fm.clone(),
693                    program,
694                    handler,
695                    opts,
696                    &fm.name,
697                    Some(&comments),
698                    |program| custom_before_pass(program),
699                )
700            })?;
701            let config = match config {
702                Some(v) => v,
703                None => {
704                    bail!("cannot process file because it's ignored by .swcrc")
705                }
706            };
707
708            let after_pass = custom_after_pass(&config.program);
709
710            let config = config.with_pass(|pass| (pass, after_pass));
711
712            let orig = if config.source_maps.enabled() {
713                self.get_orig_src_map(
714                    &fm,
715                    &config.input_source_map,
716                    config
717                        .comments
718                        .get_trailing(config.program.span_hi())
719                        .as_deref()
720                        .unwrap_or_default(),
721                    false,
722                )?
723            } else {
724                None
725            };
726
727            self.apply_transforms(handler, comments.clone(), fm.clone(), orig.as_ref(), config)
728        })
729    }
730
731    #[tracing::instrument(skip(self, handler, opts))]
732    pub fn process_js_file(
733        &self,
734        fm: Arc<SourceFile>,
735        handler: &Handler,
736        opts: &Options,
737    ) -> Result<TransformOutput, Error> {
738        self.process_js_with_custom_pass(
739            fm,
740            None,
741            handler,
742            opts,
743            SingleThreadedComments::default(),
744            |_| noop_pass(),
745            |_| noop_pass(),
746        )
747    }
748
749    #[tracing::instrument(skip_all)]
750    pub fn minify(
751        &self,
752        fm: Arc<SourceFile>,
753        handler: &Handler,
754        opts: &JsMinifyOptions,
755        extras: JsMinifyExtras,
756    ) -> Result<TransformOutput, Error> {
757        self.run(|| {
758            let _timer = timer!("Compiler::minify");
759
760            let target = opts.ecma.clone().into();
761
762            let (source_map, orig) = opts
763                .source_map
764                .as_ref()
765                .map(|obj| -> Result<_, Error> {
766                    let orig = obj.content.as_ref().map(|s| s.to_sourcemap()).transpose()?;
767
768                    Ok((SourceMapsConfig::Bool(true), orig))
769                })
770                .unwrap_as_option(|v| {
771                    Some(Ok(match v {
772                        Some(true) => (SourceMapsConfig::Bool(true), None),
773                        _ => (SourceMapsConfig::Bool(false), None),
774                    }))
775                })
776                .unwrap()?;
777
778            let mut min_opts = MinifyOptions {
779                compress: opts
780                    .compress
781                    .clone()
782                    .unwrap_as_option(|default| match default {
783                        Some(true) | None => Some(Default::default()),
784                        _ => None,
785                    })
786                    .map(|v| v.into_config(self.cm.clone())),
787                mangle: opts
788                    .mangle
789                    .clone()
790                    .unwrap_as_option(|default| match default {
791                        Some(true) | None => Some(Default::default()),
792                        _ => None,
793                    }),
794                ..Default::default()
795            };
796
797            // top_level defaults to true if module is true
798
799            // https://github.com/swc-project/swc/issues/2254
800
801            if opts.keep_fnames {
802                if let Some(opts) = &mut min_opts.compress {
803                    opts.keep_fnames = true;
804                }
805                if let Some(opts) = &mut min_opts.mangle {
806                    opts.keep_fn_names = true;
807                }
808            }
809
810            let comments = SingleThreadedComments::default();
811
812            let mut program = self
813                .parse_js(
814                    fm.clone(),
815                    handler,
816                    target,
817                    Syntax::Es(EsSyntax {
818                        jsx: true,
819                        decorators: true,
820                        decorators_before_export: true,
821                        import_attributes: true,
822                        ..Default::default()
823                    }),
824                    opts.module,
825                    Some(&comments),
826                )
827                .context("failed to parse input file")?;
828
829            if program.is_module() {
830                if let Some(opts) = &mut min_opts.compress {
831                    if opts.top_level.is_none() {
832                        opts.top_level = Some(TopLevelOptions { functions: true });
833                    }
834                }
835
836                if let Some(opts) = &mut min_opts.mangle {
837                    if opts.top_level.is_none() {
838                        opts.top_level = Some(true);
839                    }
840                }
841            }
842
843            let source_map_names = if source_map.enabled() {
844                let mut v = swc_compiler_base::IdentCollector {
845                    names: Default::default(),
846                };
847
848                program.visit_with(&mut v);
849
850                v.names
851            } else {
852                Default::default()
853            };
854
855            let unresolved_mark = Mark::new();
856            let top_level_mark = Mark::new();
857
858            let is_mangler_enabled = min_opts.mangle.is_some();
859
860            program = self.run_transform(handler, false, || {
861                program.mutate(&mut paren_remover(Some(&comments)));
862
863                program.mutate(&mut resolver(unresolved_mark, top_level_mark, false));
864
865                let mut program = swc_ecma_minifier::optimize(
866                    program,
867                    self.cm.clone(),
868                    Some(&comments),
869                    None,
870                    &min_opts,
871                    &swc_ecma_minifier::option::ExtraOptions {
872                        unresolved_mark,
873                        top_level_mark,
874                        mangle_name_cache: extras.mangle_name_cache,
875                    },
876                );
877
878                if !is_mangler_enabled {
879                    program.visit_mut_with(&mut hygiene())
880                }
881                program.mutate(&mut fixer(Some(&comments as &dyn Comments)));
882                program
883            });
884
885            let preserve_comments = opts
886                .format
887                .comments
888                .clone()
889                .into_inner()
890                .unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments));
891            swc_compiler_base::minify_file_comments(
892                &comments,
893                preserve_comments,
894                opts.format.preserve_annotations,
895            );
896
897            let ret = self.print(
898                &program,
899                PrintArgs {
900                    source_root: None,
901                    source_file_name: Some(&fm.name.to_string()),
902                    output_path: opts.output_path.clone().map(From::from),
903                    inline_sources_content: opts.inline_sources_content,
904                    source_map,
905                    source_map_names: &source_map_names,
906                    orig: orig.as_ref(),
907                    comments: Some(&comments),
908                    emit_source_map_columns: opts.emit_source_map_columns,
909                    preamble: &opts.format.preamble,
910                    codegen_config: swc_ecma_codegen::Config::default()
911                        .with_target(target)
912                        .with_minify(true)
913                        .with_ascii_only(opts.format.ascii_only)
914                        .with_emit_assert_for_import_attributes(
915                            opts.format.emit_assert_for_import_attributes,
916                        )
917                        .with_inline_script(opts.format.inline_script),
918                    output: None,
919                },
920            );
921
922            ret.map(|mut output| {
923                output.diagnostics = handler.take_diagnostics();
924
925                output
926            })
927        })
928    }
929
930    /// You can use custom pass with this method.
931    ///
932    /// There exists a [PassBuilder] to help building custom passes.
933    #[tracing::instrument(skip_all)]
934    pub fn process_js(
935        &self,
936        handler: &Handler,
937        program: Program,
938        opts: &Options,
939    ) -> Result<TransformOutput, Error> {
940        let loc = self.cm.lookup_char_pos(program.span().lo());
941        let fm = loc.file;
942
943        self.process_js_with_custom_pass(
944            fm,
945            Some(program),
946            handler,
947            opts,
948            SingleThreadedComments::default(),
949            |_| noop_pass(),
950            |_| noop_pass(),
951        )
952    }
953
954    #[tracing::instrument(name = "swc::Compiler::apply_transforms", skip_all)]
955    fn apply_transforms(
956        &self,
957        handler: &Handler,
958        comments: SingleThreadedComments,
959        fm: Arc<SourceFile>,
960        orig: Option<&sourcemap::SourceMap>,
961        config: BuiltInput<impl Pass>,
962    ) -> Result<TransformOutput, Error> {
963        self.run(|| {
964            let program = config.program;
965
966            if config.emit_isolated_dts && !config.syntax.typescript() {
967                handler.warn(
968                    "jsc.experimental.emitIsolatedDts is enabled but the syntax is not TypeScript",
969                );
970            }
971
972            let emit_dts = config.syntax.typescript() && config.emit_isolated_dts;
973            let source_map_names = if config.source_maps.enabled() {
974                let mut v = swc_compiler_base::IdentCollector {
975                    names: Default::default(),
976                };
977
978                program.visit_with(&mut v);
979
980                v.names
981            } else {
982                Default::default()
983            };
984
985            let dts_code = if emit_dts {
986                let (leading, trailing) = comments.borrow_all();
987
988                let leading = std::rc::Rc::new(RefCell::new(leading.clone()));
989                let trailing = std::rc::Rc::new(RefCell::new(trailing.clone()));
990
991                let comments = SingleThreadedComments::from_leading_and_trailing(leading, trailing);
992                let mut checker =
993                    FastDts::new(fm.name.clone(), config.unresolved_mark, Default::default());
994                let mut program = program.clone();
995
996                if let Some((base, resolver)) = config.resolver {
997                    program.mutate(import_rewriter(base, resolver));
998                }
999
1000                let issues = checker.transform(&mut program);
1001
1002                for issue in issues {
1003                    handler
1004                        .struct_span_err(issue.range.span, &issue.message)
1005                        .emit();
1006                }
1007
1008                let dts_code = to_code_with_comments(Some(&comments), &program);
1009                Some(dts_code)
1010            } else {
1011                None
1012            };
1013
1014            let pass = config.pass;
1015            let (program, output) = swc_transform_common::output::capture(|| {
1016                if let Some(dts_code) = dts_code {
1017                    emit(
1018                        "__swc_isolated_declarations__".into(),
1019                        serde_json::Value::String(dts_code),
1020                    );
1021                }
1022
1023                helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
1024                    HANDLER.set(handler, || {
1025                        // Fold module
1026                        program.apply(pass)
1027                    })
1028                })
1029            });
1030
1031            if let Some(comments) = &config.comments {
1032                swc_compiler_base::minify_file_comments(
1033                    comments,
1034                    config.preserve_comments,
1035                    config.output.preserve_annotations.into_bool(),
1036                );
1037            }
1038
1039            self.print(
1040                &program,
1041                PrintArgs {
1042                    source_root: config.source_root.as_deref(),
1043                    source_file_name: config.source_file_name.as_deref(),
1044                    output_path: config.output_path,
1045                    inline_sources_content: config.inline_sources_content,
1046                    source_map: config.source_maps,
1047                    source_map_names: &source_map_names,
1048                    orig,
1049                    comments: config.comments.as_ref().map(|v| v as _),
1050                    emit_source_map_columns: config.emit_source_map_columns,
1051                    preamble: &config.output.preamble,
1052                    codegen_config: swc_ecma_codegen::Config::default()
1053                        .with_target(config.target)
1054                        .with_minify(config.minify)
1055                        .with_ascii_only(
1056                            config
1057                                .output
1058                                .charset
1059                                .map(|v| matches!(v, OutputCharset::Ascii))
1060                                .unwrap_or(false),
1061                        )
1062                        .with_emit_assert_for_import_attributes(
1063                            config.emit_assert_for_import_attributes,
1064                        )
1065                        .with_inline_script(config.codegen_inline_script),
1066                    output: if output.is_empty() {
1067                        None
1068                    } else {
1069                        Some(output)
1070                    },
1071                },
1072            )
1073        })
1074    }
1075}
1076
1077#[non_exhaustive]
1078#[derive(Clone, Default)]
1079pub struct JsMinifyExtras {
1080    pub mangle_name_cache: Option<Arc<dyn MangleCache>>,
1081}
1082
1083impl JsMinifyExtras {
1084    pub fn with_mangle_name_cache(
1085        mut self,
1086        mangle_name_cache: Option<Arc<dyn MangleCache>>,
1087    ) -> Self {
1088        self.mangle_name_cache = mangle_name_cache;
1089        self
1090    }
1091}
1092
1093fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option<PathBuf> {
1094    let mut parent = path.parent();
1095    while let Some(dir) = parent {
1096        let swcrc = dir.join(".swcrc");
1097
1098        if swcrc.exists() {
1099            return Some(swcrc);
1100        }
1101
1102        if dir == root && root_mode == RootMode::Root {
1103            break;
1104        }
1105        parent = dir.parent();
1106    }
1107
1108    None
1109}
1110
1111#[tracing::instrument(skip_all)]
1112fn load_swcrc(path: &Path) -> Result<Rc, Error> {
1113    let content = read_to_string(path).context("failed to read config (.swcrc) file")?;
1114
1115    parse_swcrc(&content)
1116}
1117
1118fn parse_swcrc(s: &str) -> Result<Rc, Error> {
1119    fn convert_json_err(e: serde_json::Error) -> Error {
1120        let line = e.line();
1121        let column = e.column();
1122
1123        let msg = match e.classify() {
1124            Category::Io => "io error",
1125            Category::Syntax => "syntax error",
1126            Category::Data => "unmatched data",
1127            Category::Eof => "unexpected eof",
1128        };
1129        Error::new(e).context(format!(
1130            "failed to deserialize .swcrc (json) file: {}: {}:{}",
1131            msg, line, column
1132        ))
1133    }
1134
1135    let v = parse_to_serde_value(
1136        s.trim_start_matches('\u{feff}'),
1137        &ParseOptions {
1138            allow_comments: true,
1139            allow_trailing_commas: true,
1140            allow_loose_object_property_names: false,
1141        },
1142    )?
1143    .ok_or_else(|| Error::msg("failed to deserialize empty .swcrc (json) file"))?;
1144
1145    if let Ok(rc) = serde_json::from_value(v.clone()) {
1146        return Ok(rc);
1147    }
1148
1149    serde_json::from_value(v)
1150        .map(Rc::Single)
1151        .map_err(convert_json_err)
1152}