Skip to main content

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    fs::{read_to_string, File},
117    io::ErrorKind,
118    path::{Path, PathBuf},
119    sync::Arc,
120};
121
122use anyhow::{bail, Context, Error};
123use base64::prelude::{Engine, BASE64_STANDARD};
124use common::{
125    comments::{Comment, SingleThreadedComments},
126    errors::HANDLER,
127};
128use jsonc_parser::{parse_to_serde_value, ParseOptions};
129use once_cell::sync::Lazy;
130use serde_json::error::Category;
131use swc_common::{
132    comments::Comments, errors::Handler, sync::Lrc, FileName, Mark, SourceFile, SourceMap, Spanned,
133    GLOBALS,
134};
135pub use swc_compiler_base::{PrintArgs, TransformOutput};
136pub use swc_config::types::{BoolConfig, BoolOr, BoolOrDataConfig};
137use swc_ecma_ast::{noop_pass, EsVersion, Pass, Program};
138use swc_ecma_codegen::Node;
139#[cfg(feature = "module")]
140use swc_ecma_loader::resolvers::{
141    lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
142};
143use swc_ecma_minifier::option::{MangleCache, MinifyOptions, TopLevelOptions};
144use swc_ecma_parser::{EsSyntax, Syntax};
145use swc_ecma_transforms::{
146    fixer,
147    helpers::{self, Helpers},
148    hygiene, resolver,
149};
150use swc_ecma_transforms_base::fixer::paren_remover;
151#[cfg(feature = "module")]
152use swc_ecma_transforms_module::path::NodeImportResolver;
153use swc_ecma_visit::{FoldWith, VisitMutWith, VisitWith};
154pub use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
155pub use swc_node_comments::SwcComments;
156pub use swc_sourcemap as sourcemap;
157use swc_timer::timer;
158#[cfg(feature = "isolated-dts")]
159use swc_typescript::fast_dts::FastDts;
160use tracing::warn;
161use url::Url;
162
163use crate::config::{
164    BuiltInput, Config, ConfigFile, InputSourceMap, IsModule, JsMinifyCommentOption,
165    JsMinifyOptions, Options, OutputCharset, Rc, RootMode, SourceMapsConfig,
166};
167
168mod builder;
169pub mod config;
170mod dropped_comments_preserver;
171mod plugin;
172pub mod wasm_analysis;
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
213#[cfg(feature = "module")]
214type SwcImportResolver = Arc<
215    NodeImportResolver<CachingResolver<TsConfigResolver<CachingResolver<NodeModulesResolver>>>>,
216>;
217
218/// All methods accept [Handler], which is a storage for errors.
219///
220/// The caller should check if the handler contains any errors after calling
221/// method.
222pub struct Compiler {
223    /// CodeMap
224    pub cm: Arc<SourceMap>,
225    comments: SwcComments,
226}
227
228/// These are **low-level** apis.
229impl Compiler {
230    pub fn comments(&self) -> &SwcComments {
231        &self.comments
232    }
233
234    /// Runs `op` in current compiler's context.
235    ///
236    /// Note: Other methods of `Compiler` already uses this internally.
237    pub fn run<R, F>(&self, op: F) -> R
238    where
239        F: FnOnce() -> R,
240    {
241        debug_assert!(
242            GLOBALS.is_set(),
243            "`swc_common::GLOBALS` is required for this operation"
244        );
245
246        op()
247    }
248
249    fn get_orig_src_map(
250        &self,
251        fm: &SourceFile,
252        input_src_map: &InputSourceMap,
253        comments: &[Comment],
254        is_default: bool,
255    ) -> Result<Option<sourcemap::SourceMap>, Error> {
256        self.run(|| -> Result<_, Error> {
257            let name = &fm.name;
258
259            let read_inline_sourcemap =
260                |data_url: &str| -> Result<Option<sourcemap::SourceMap>, Error> {
261                    let url = Url::parse(data_url).with_context(|| {
262                        format!("failed to parse inline source map url\n{data_url}")
263                    })?;
264
265                    let idx = match url.path().find("base64,") {
266                        Some(v) => v,
267                        None => {
268                            bail!("failed to parse inline source map: not base64: {url:?}")
269                        }
270                    };
271
272                    let content = url.path()[idx + "base64,".len()..].trim();
273
274                    let res = BASE64_STANDARD
275                        .decode(content.as_bytes())
276                        .context("failed to decode base64-encoded source map")?;
277
278                    Ok(Some(sourcemap::SourceMap::from_slice(&res).context(
279                        "failed to read input source map from inlined base64 encoded string",
280                    )?))
281                };
282
283            let read_file_sourcemap =
284                |data_url: Option<&str>| -> Result<Option<sourcemap::SourceMap>, Error> {
285                    match &**name {
286                        FileName::Real(filename) => {
287                            let dir = match filename.parent() {
288                                Some(v) => v,
289                                None => {
290                                    bail!("unexpected: root directory is given as a input file")
291                                }
292                            };
293
294                            let map_path = match data_url {
295                                Some(data_url) => {
296                                    let mut map_path = dir.join(data_url);
297                                    if !map_path.exists() {
298                                        // Old behavior. This check would prevent
299                                        // regressions.
300                                        // Perhaps it shouldn't be supported. Sometimes
301                                        // developers don't want to expose their source
302                                        // code.
303                                        // Map files are for internal troubleshooting
304                                        // convenience.
305                                        let fallback_map_path =
306                                            PathBuf::from(format!("{}.map", filename.display()));
307                                        if fallback_map_path.exists() {
308                                            map_path = fallback_map_path;
309                                        } else {
310                                            bail!(
311                                                "failed to find input source map file {:?} in \
312                                                 {:?} file as either {:?} or with appended .map",
313                                                data_url,
314                                                filename.display(),
315                                                map_path.display(),
316                                            )
317                                        }
318                                    }
319
320                                    Some(map_path)
321                                }
322                                None => {
323                                    // Old behavior.
324                                    let map_path =
325                                        PathBuf::from(format!("{}.map", filename.display()));
326                                    if map_path.exists() {
327                                        Some(map_path)
328                                    } else {
329                                        None
330                                    }
331                                }
332                            };
333
334                            match map_path {
335                                Some(map_path) => {
336                                    let path = map_path.display().to_string();
337                                    let file = File::open(&path);
338
339                                    // If file is not found, we should return None.
340                                    // Some libraries generates source map but omit them from the
341                                    // npm package.
342                                    //
343                                    // See https://github.com/swc-project/swc/issues/8789#issuecomment-2105055772
344                                    if file
345                                        .as_ref()
346                                        .is_err_and(|err| err.kind() == ErrorKind::NotFound)
347                                    {
348                                        warn!(
349                                            "source map is specified by sourceMappingURL but \
350                                             there's no source map at `{}`",
351                                            path
352                                        );
353                                        return Ok(None);
354                                    }
355
356                                    // Old behavior.
357                                    let file = if !is_default {
358                                        file?
359                                    } else {
360                                        match file {
361                                            Ok(v) => v,
362                                            Err(_) => return Ok(None),
363                                        }
364                                    };
365
366                                    Ok(Some(sourcemap::SourceMap::from_reader(file).with_context(
367                                        || {
368                                            format!(
369                                                "failed to read input source map
370                                from file at {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                            swc_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                            // Canonicalize relative paths for proper parent traversal
497                            let abs_path = if path.is_relative() {
498                                root.join(path).canonicalize().ok()
499                            } else {
500                                path.canonicalize().ok()
501                            };
502                            let found = abs_path.and_then(|p| find_swcrc(&p, root, *root_mode));
503
504                            // "upward" mode requires a .swcrc to be found
505                            if found.is_none() && *root_mode == RootMode::Upward {
506                                bail!(
507                                    "Could not find .swcrc file while using rootMode \
508                                     \"upward\".\nSearched from: {}",
509                                    path.display()
510                                );
511                            }
512
513                            found
514                        } else {
515                            None
516                        }
517                    } else {
518                        None
519                    }
520                }
521            };
522
523            let config_file = match swcrc_path.as_deref() {
524                Some(s) => Some(load_swcrc(s)?),
525                _ => None,
526            };
527            let filename_path = match name {
528                FileName::Real(p) => Some(&**p),
529                _ => None,
530            };
531
532            if let Some(filename_path) = filename_path {
533                if let Some(config) = config_file {
534                    let dir = swcrc_path
535                        .as_deref()
536                        .and_then(|p| p.parent())
537                        .expect(".swcrc path should have parent dir");
538
539                    let mut config = config
540                        .into_config(Some(filename_path))
541                        .context("failed to process config file")?;
542
543                    if let Some(c) = &mut config {
544                        if c.jsc.base_url != PathBuf::new() {
545                            let joined = dir.join(&c.jsc.base_url);
546                            c.jsc.base_url = if cfg!(target_os = "windows")
547                                && c.jsc.base_url.as_os_str() == "."
548                            {
549                                dir.canonicalize().with_context(|| {
550                                    format!(
551                                        "failed to canonicalize base url using the path of \
552                                         .swcrc\nDir: {}\n(Used logic for windows)",
553                                        dir.display(),
554                                    )
555                                })?
556                            } else {
557                                joined.canonicalize().with_context(|| {
558                                    format!(
559                                        "failed to canonicalize base url using the path of \
560                                         .swcrc\nPath: {}\nDir: {}\nbaseUrl: {}",
561                                        joined.display(),
562                                        dir.display(),
563                                        c.jsc.base_url.display()
564                                    )
565                                })?
566                            };
567                        }
568                    }
569
570                    return Ok(config);
571                }
572
573                let config_file = config_file.unwrap_or_default();
574                let config = config_file.into_config(Some(filename_path))?;
575
576                return Ok(config);
577            }
578
579            let config = match config_file {
580                Some(config_file) => config_file.into_config(None)?,
581                None => Rc::default().into_config(None)?,
582            };
583
584            match config {
585                Some(config) => Ok(Some(config)),
586                None => {
587                    bail!("no config matched for file ({name})")
588                }
589            }
590        })
591        .with_context(|| format!("failed to read .swcrc file for input file at `{name}`"))
592    }
593
594    /// This method returns [None] if a file should be skipped.
595    ///
596    /// This method handles merging of config.
597    ///
598    /// This method does **not** parse module.
599    #[tracing::instrument(skip_all)]
600    pub fn parse_js_as_input<'a, P>(
601        &'a self,
602        fm: Lrc<SourceFile>,
603        program: Option<Program>,
604        handler: &'a Handler,
605        opts: &Options,
606        name: &FileName,
607        comments: Option<&'a SingleThreadedComments>,
608        before_pass: impl 'a + FnOnce(&Program) -> P,
609    ) -> Result<Option<BuiltInput<impl 'a + Pass>>, Error>
610    where
611        P: 'a + Pass,
612    {
613        self.run(move || {
614            let _timer = timer!("Compiler.parse");
615
616            if let FileName::Real(ref path) = name {
617                if !opts.config.matches(path)? {
618                    return Ok(None);
619                }
620            }
621
622            let config = self.read_config(opts, name)?;
623            let config = match config {
624                Some(v) => v,
625                None => return Ok(None),
626            };
627
628            let built = opts.build_as_input(
629                &self.cm,
630                name,
631                move |syntax, target, is_module| match program {
632                    Some(v) => Ok(v),
633                    _ => self.parse_js(
634                        fm.clone(),
635                        handler,
636                        target,
637                        syntax,
638                        is_module,
639                        comments.as_ref().map(|v| v as _),
640                    ),
641                },
642                opts.output_path.as_deref(),
643                opts.source_root.clone(),
644                opts.source_file_name.clone(),
645                config.source_map_ignore_list.clone(),
646                handler,
647                Some(config),
648                comments,
649                before_pass,
650            )?;
651            Ok(Some(built))
652        })
653    }
654
655    pub fn run_transform<F, Ret>(&self, handler: &Handler, external_helpers: bool, op: F) -> Ret
656    where
657        F: FnOnce() -> Ret,
658    {
659        self.run(|| {
660            helpers::HELPERS.set(&Helpers::new(external_helpers), || HANDLER.set(handler, op))
661        })
662    }
663
664    #[tracing::instrument(skip_all)]
665    pub fn transform(
666        &self,
667        handler: &Handler,
668        program: Program,
669        external_helpers: bool,
670        mut pass: impl swc_ecma_visit::Fold,
671    ) -> Program {
672        self.run_transform(handler, external_helpers, || {
673            // Fold module
674            program.fold_with(&mut pass)
675        })
676    }
677
678    /// `custom_after_pass` is applied after swc transforms are applied.
679    ///
680    /// `program`: If you already parsed `Program`, you can pass it.
681    ///
682    /// # Guarantee
683    ///
684    /// `swc` invokes `custom_before_pass` after
685    ///
686    ///  - Handling decorators, if configured
687    ///  - Applying `resolver`
688    ///  - Stripping typescript nodes
689    ///
690    /// This means, you can use `noop_visit_type`, `noop_fold_type` and
691    /// `noop_visit_mut_type` in your visitor to reduce the binary size.
692    #[tracing::instrument(skip_all)]
693    pub fn process_js_with_custom_pass<P1, P2>(
694        &self,
695        fm: Arc<SourceFile>,
696        program: Option<Program>,
697        handler: &Handler,
698        opts: &Options,
699        comments: SingleThreadedComments,
700        custom_before_pass: impl FnOnce(&Program) -> P1,
701        custom_after_pass: impl FnOnce(&Program) -> P2,
702    ) -> Result<TransformOutput, Error>
703    where
704        P1: Pass,
705        P2: Pass,
706    {
707        self.run(|| -> Result<_, Error> {
708            let config = self.run(|| {
709                self.parse_js_as_input(
710                    fm.clone(),
711                    program,
712                    handler,
713                    opts,
714                    &fm.name,
715                    Some(&comments),
716                    |program| custom_before_pass(program),
717                )
718            })?;
719            let config = match config {
720                Some(v) => v,
721                None => {
722                    bail!("cannot process file because it's ignored by .swcrc")
723                }
724            };
725
726            let after_pass = custom_after_pass(&config.program);
727
728            let config = config.with_pass(|pass| (pass, after_pass));
729
730            let orig = if config.source_maps.enabled() {
731                self.get_orig_src_map(
732                    &fm,
733                    &config.input_source_map,
734                    config
735                        .comments
736                        .get_trailing(config.program.span_hi())
737                        .as_deref()
738                        .unwrap_or_default(),
739                    false,
740                )?
741            } else {
742                None
743            };
744
745            self.apply_transforms(handler, comments.clone(), fm.clone(), orig, config)
746        })
747    }
748
749    #[tracing::instrument(skip(self, handler, opts))]
750    pub fn process_js_file(
751        &self,
752        fm: Arc<SourceFile>,
753        handler: &Handler,
754        opts: &Options,
755    ) -> Result<TransformOutput, Error> {
756        self.process_js_with_custom_pass(
757            fm,
758            None,
759            handler,
760            opts,
761            SingleThreadedComments::default(),
762            |_| noop_pass(),
763            |_| noop_pass(),
764        )
765    }
766
767    #[tracing::instrument(skip_all)]
768    pub fn minify(
769        &self,
770        fm: Arc<SourceFile>,
771        handler: &Handler,
772        opts: &JsMinifyOptions,
773        extras: JsMinifyExtras,
774    ) -> Result<TransformOutput, Error> {
775        self.run(|| {
776            let _timer = timer!("Compiler::minify");
777
778            let target = opts.ecma.clone().into();
779
780            let (source_map, orig, source_map_url) = opts
781                .source_map
782                .as_ref()
783                .map(|obj| -> Result<_, Error> {
784                    let orig = obj.content.as_ref().map(|s| s.to_sourcemap()).transpose()?;
785
786                    Ok((SourceMapsConfig::Bool(true), orig, obj.url.as_deref()))
787                })
788                .unwrap_as_option(|v| {
789                    Some(Ok(match v {
790                        Some(true) => (SourceMapsConfig::Bool(true), None, None),
791                        _ => (SourceMapsConfig::Bool(false), None, None),
792                    }))
793                })
794                .unwrap()?;
795
796            let mut min_opts = MinifyOptions {
797                compress: opts
798                    .compress
799                    .clone()
800                    .unwrap_as_option(|default| match default {
801                        Some(true) | None => Some(Default::default()),
802                        _ => None,
803                    })
804                    .map(|v| v.into_config(self.cm.clone())),
805                mangle: opts
806                    .mangle
807                    .clone()
808                    .unwrap_as_option(|default| match default {
809                        Some(true) | None => Some(Default::default()),
810                        _ => None,
811                    }),
812                ..Default::default()
813            };
814
815            // top_level defaults to true if module is true
816
817            // https://github.com/swc-project/swc/issues/2254
818
819            if opts.keep_fnames {
820                if let Some(opts) = &mut min_opts.compress {
821                    opts.keep_fnames = true;
822                }
823                if let Some(opts) = &mut min_opts.mangle {
824                    opts.keep_fn_names = true;
825                }
826            }
827
828            let comments = SingleThreadedComments::default();
829
830            let mut program = self
831                .parse_js(
832                    fm.clone(),
833                    handler,
834                    target,
835                    Syntax::Es(EsSyntax {
836                        jsx: true,
837                        decorators: true,
838                        decorators_before_export: true,
839                        import_attributes: true,
840                        ..Default::default()
841                    }),
842                    opts.module,
843                    Some(&comments),
844                )
845                .context("failed to parse input file")?;
846
847            if opts.toplevel == Some(true) || program.is_module() {
848                if let Some(opts) = &mut min_opts.compress {
849                    if opts.top_level.is_none() {
850                        opts.top_level = Some(TopLevelOptions { functions: true });
851                    }
852                }
853
854                if let Some(opts) = &mut min_opts.mangle {
855                    if opts.top_level.is_none() {
856                        opts.top_level = Some(true);
857                    }
858                }
859            }
860
861            let source_map_names = if source_map.enabled() {
862                let mut v = swc_compiler_base::IdentCollector {
863                    names: Default::default(),
864                };
865
866                program.visit_with(&mut v);
867
868                v.names
869            } else {
870                Default::default()
871            };
872
873            let unresolved_mark = Mark::new();
874            let top_level_mark = Mark::new();
875
876            let is_mangler_enabled = min_opts.mangle.is_some();
877
878            program = self.run_transform(handler, false, || {
879                program.mutate(&mut paren_remover(Some(&comments)));
880
881                program.mutate(&mut resolver(unresolved_mark, top_level_mark, false));
882
883                let mut program = swc_ecma_minifier::optimize(
884                    program,
885                    self.cm.clone(),
886                    Some(&comments),
887                    None,
888                    &min_opts,
889                    &swc_ecma_minifier::option::ExtraOptions {
890                        unresolved_mark,
891                        top_level_mark,
892                        mangle_name_cache: extras.mangle_name_cache,
893                    },
894                );
895
896                if !is_mangler_enabled {
897                    program.visit_mut_with(&mut hygiene())
898                }
899                program.mutate(&mut fixer(Some(&comments as &dyn Comments)));
900                program
901            });
902
903            let preserve_comments = opts
904                .format
905                .comments
906                .clone()
907                .into_inner()
908                .unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments));
909            swc_compiler_base::minify_file_comments(
910                &comments,
911                preserve_comments,
912                opts.format.preserve_annotations,
913            );
914
915            let ret = self.print(
916                &program,
917                PrintArgs {
918                    source_root: None,
919                    source_file_name: Some(&fm.name.to_string()),
920                    output_path: opts.output_path.clone().map(From::from),
921                    inline_sources_content: opts.inline_sources_content,
922                    source_map,
923                    source_map_ignore_list: opts.source_map_ignore_list.clone(),
924                    source_map_names: &source_map_names,
925                    orig,
926                    comments: Some(&comments),
927                    emit_source_map_columns: opts.emit_source_map_columns,
928                    emit_source_map_scopes: false,
929                    preamble: &opts.format.preamble,
930                    codegen_config: swc_ecma_codegen::Config::default()
931                        .with_target(target)
932                        .with_minify(true)
933                        .with_ascii_only(opts.format.ascii_only)
934                        .with_emit_assert_for_import_attributes(
935                            opts.format.emit_assert_for_import_attributes,
936                        )
937                        .with_inline_script(opts.format.inline_script)
938                        .with_reduce_escaped_newline(
939                            min_opts
940                                .compress
941                                .unwrap_or_default()
942                                .experimental
943                                .reduce_escaped_newline,
944                        ),
945                    output: None,
946                    source_map_url,
947                },
948            );
949
950            ret.map(|mut output| {
951                output.diagnostics = handler.take_diagnostics();
952
953                output
954            })
955        })
956    }
957
958    /// You can use custom pass with this method.
959    ///
960    /// Pass building logic has been inlined into the configuration system.
961    #[tracing::instrument(skip_all)]
962    pub fn process_js(
963        &self,
964        handler: &Handler,
965        program: Program,
966        opts: &Options,
967    ) -> Result<TransformOutput, Error> {
968        let loc = self.cm.lookup_char_pos(program.span().lo());
969        let fm = loc.file;
970
971        self.process_js_with_custom_pass(
972            fm,
973            Some(program),
974            handler,
975            opts,
976            SingleThreadedComments::default(),
977            |_| noop_pass(),
978            |_| noop_pass(),
979        )
980    }
981
982    #[tracing::instrument(name = "swc::Compiler::apply_transforms", skip_all)]
983    fn apply_transforms(
984        &self,
985        handler: &Handler,
986        #[allow(unused)] comments: SingleThreadedComments,
987        #[allow(unused)] fm: Arc<SourceFile>,
988        orig: Option<sourcemap::SourceMap>,
989        config: BuiltInput<impl Pass>,
990    ) -> Result<TransformOutput, Error> {
991        self.run(|| {
992            let program = config.program;
993            let is_typescript_syntax = matches!(config.syntax, Syntax::Typescript(..));
994
995            if config.emit_isolated_dts && !is_typescript_syntax {
996                handler.warn(
997                    "jsc.experimental.emitIsolatedDts is enabled but the syntax is not TypeScript",
998                );
999            }
1000
1001            let source_map_names = if config.source_maps.enabled() {
1002                let mut v = swc_compiler_base::IdentCollector {
1003                    names: Default::default(),
1004                };
1005
1006                program.visit_with(&mut v);
1007
1008                v.names
1009            } else {
1010                Default::default()
1011            };
1012            #[cfg(feature = "isolated-dts")]
1013            let dts_code = if is_typescript_syntax && config.emit_isolated_dts {
1014                use std::cell::RefCell;
1015
1016                use swc_ecma_codegen::to_code_with_comments;
1017                let (leading, trailing) = comments.borrow_all();
1018
1019                let leading = std::rc::Rc::new(RefCell::new(leading.clone()));
1020                let trailing = std::rc::Rc::new(RefCell::new(trailing.clone()));
1021
1022                let comments = SingleThreadedComments::from_leading_and_trailing(leading, trailing);
1023
1024                let mut checker =
1025                    FastDts::new(fm.name.clone(), config.unresolved_mark, Default::default());
1026                let mut program = program.clone();
1027
1028                #[cfg(feature = "module")]
1029                if let Some((base, resolver)) = config.resolver {
1030                    use swc_ecma_transforms_module::rewriter::import_rewriter;
1031
1032                    program.mutate(import_rewriter(base, resolver));
1033                }
1034
1035                let issues = checker.transform(&mut program);
1036
1037                for issue in issues {
1038                    handler
1039                        .struct_span_err(issue.range.span, &issue.message)
1040                        .emit();
1041                }
1042
1043                let dts_code = to_code_with_comments(Some(&comments), &program);
1044                Some(dts_code)
1045            } else {
1046                None
1047            };
1048
1049            let pass = config.pass;
1050            let (program, output) = swc_transform_common::output::capture(|| {
1051                #[cfg(feature = "isolated-dts")]
1052                {
1053                    if let Some(dts_code) = dts_code {
1054                        use swc_transform_common::output::experimental_emit;
1055                        experimental_emit("__swc_isolated_declarations__".into(), dts_code);
1056                    }
1057                }
1058
1059                helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
1060                    HANDLER.set(handler, || {
1061                        // Fold module
1062                        program.apply(pass)
1063                    })
1064                })
1065            });
1066
1067            if let Some(comments) = &config.comments {
1068                swc_compiler_base::minify_file_comments(
1069                    comments,
1070                    config.preserve_comments,
1071                    config.output.preserve_annotations.into_bool(),
1072                );
1073            }
1074
1075            self.print(
1076                &program,
1077                PrintArgs {
1078                    source_root: config.source_root.as_deref(),
1079                    source_file_name: config.source_file_name.as_deref(),
1080                    source_map_ignore_list: config.source_map_ignore_list.clone(),
1081                    output_path: config.output_path,
1082                    inline_sources_content: config.inline_sources_content,
1083                    source_map: config.source_maps,
1084                    source_map_names: &source_map_names,
1085                    orig,
1086                    comments: config.comments.as_ref().map(|v| v as _),
1087                    emit_source_map_columns: config.emit_source_map_columns,
1088                    emit_source_map_scopes: config.emit_source_map_scopes,
1089                    preamble: &config.output.preamble,
1090                    codegen_config: swc_ecma_codegen::Config::default()
1091                        .with_target(config.target)
1092                        .with_minify(config.minify)
1093                        .with_ascii_only(
1094                            config
1095                                .output
1096                                .charset
1097                                .map(|v| matches!(v, OutputCharset::Ascii))
1098                                .unwrap_or(false),
1099                        )
1100                        .with_emit_assert_for_import_attributes(
1101                            config.emit_assert_for_import_attributes,
1102                        )
1103                        .with_inline_script(config.codegen_inline_script),
1104                    output: if output.is_empty() {
1105                        None
1106                    } else {
1107                        Some(output)
1108                    },
1109                    source_map_url: config.output.source_map_url.as_deref(),
1110                },
1111            )
1112        })
1113    }
1114}
1115
1116#[non_exhaustive]
1117#[derive(Clone, Default)]
1118pub struct JsMinifyExtras {
1119    pub mangle_name_cache: Option<Arc<dyn MangleCache>>,
1120}
1121
1122impl JsMinifyExtras {
1123    pub fn with_mangle_name_cache(
1124        mut self,
1125        mangle_name_cache: Option<Arc<dyn MangleCache>>,
1126    ) -> Self {
1127        self.mangle_name_cache = mangle_name_cache;
1128        self
1129    }
1130}
1131
1132fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option<PathBuf> {
1133    let mut parent = path.parent();
1134    while let Some(dir) = parent {
1135        let swcrc = dir.join(".swcrc");
1136
1137        if swcrc.exists() {
1138            return Some(swcrc);
1139        }
1140
1141        if dir == root && root_mode == RootMode::Root {
1142            break;
1143        }
1144        parent = dir.parent();
1145    }
1146
1147    None
1148}
1149
1150#[tracing::instrument(skip_all)]
1151fn load_swcrc(path: &Path) -> Result<Rc, Error> {
1152    let content = read_to_string(path).context("failed to read config (.swcrc) file")?;
1153
1154    parse_swcrc(&content)
1155}
1156
1157fn parse_swcrc(s: &str) -> Result<Rc, Error> {
1158    fn convert_json_err(e: serde_json::Error) -> Error {
1159        let line = e.line();
1160        let column = e.column();
1161
1162        let msg = match e.classify() {
1163            Category::Io => "io error",
1164            Category::Syntax => "syntax error",
1165            Category::Data => "unmatched data",
1166            Category::Eof => "unexpected eof",
1167        };
1168        Error::new(e).context(format!(
1169            "failed to deserialize .swcrc (json) file: {msg}: {line}:{column}"
1170        ))
1171    }
1172
1173    let v = parse_to_serde_value(
1174        s.trim_start_matches('\u{feff}'),
1175        &ParseOptions {
1176            allow_comments: true,
1177            allow_trailing_commas: true,
1178            allow_loose_object_property_names: false,
1179        },
1180    )?
1181    .ok_or_else(|| Error::msg("failed to deserialize empty .swcrc (json) file"))?;
1182
1183    if let Ok(rc) = serde_json::from_value(v.clone()) {
1184        return Ok(rc);
1185    }
1186
1187    serde_json::from_value(v)
1188        .map(Rc::Single)
1189        .map_err(convert_json_err)
1190}