1#![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;
132use swc_common::{
133 comments::Comments, errors::Handler, sync::Lrc, FileName, Mark, SourceFile, SourceMap, Spanned,
134 GLOBALS,
135};
136pub use swc_compiler_base::{PrintArgs, TransformOutput};
137pub use swc_config::types::{BoolConfig, BoolOr, BoolOrDataConfig};
138use swc_ecma_ast::{noop_pass, EsVersion, Pass, Program};
139use swc_ecma_codegen::{to_code_with_comments, Node};
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,
149 modules::{path::NodeImportResolver, rewriter::import_rewriter},
150 resolver,
151};
152use swc_ecma_transforms_base::fixer::paren_remover;
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;
158use swc_transform_common::output::experimental_emit;
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
213type SwcImportResolver = Arc<
214 NodeImportResolver<CachingResolver<TsConfigResolver<CachingResolver<NodeModulesResolver>>>>,
215>;
216
217pub struct Compiler {
222 pub cm: Arc<SourceMap>,
224 comments: SwcComments,
225}
226
227impl Compiler {
229 pub fn comments(&self) -> &SwcComments {
230 &self.comments
231 }
232
233 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 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 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
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 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 {path}"
370 )
371 },
372 )?))
373 }
374 None => Ok(None),
375 }
376 }
377 _ => Ok(None),
378 }
379 };
380
381 let read_sourcemap = || -> Option<sourcemap::SourceMap> {
382 let s = "sourceMappingURL=";
383
384 let text = comments.iter().rev().find_map(|c| {
385 let idx = c.text.rfind(s)?;
386 let (_, url) = c.text.split_at(idx + s.len());
387
388 Some(url.trim())
389 });
390
391 let result = match text {
393 Some(text) if text.starts_with("data:") => read_inline_sourcemap(text),
394 _ => read_file_sourcemap(text),
395 };
396 match result {
397 Ok(r) => r,
398 Err(err) => {
399 tracing::error!("failed to read input source map: {:?}", err);
400 None
401 }
402 }
403 };
404
405 match input_src_map {
407 InputSourceMap::Bool(false) => Ok(None),
408 InputSourceMap::Bool(true) => Ok(read_sourcemap()),
409 InputSourceMap::Str(ref s) => {
410 if s == "inline" {
411 Ok(read_sourcemap())
412 } else {
413 Ok(Some(
415 swc_sourcemap::SourceMap::from_slice(s.as_bytes()).context(
416 "failed to read input source map from user-provided sourcemap",
417 )?,
418 ))
419 }
420 }
421 }
422 })
423 }
424
425 pub fn parse_js(
427 &self,
428 fm: Arc<SourceFile>,
429 handler: &Handler,
430 target: EsVersion,
431 syntax: Syntax,
432 is_module: IsModule,
433 comments: Option<&dyn Comments>,
434 ) -> Result<Program, Error> {
435 swc_compiler_base::parse_js(
436 self.cm.clone(),
437 fm,
438 handler,
439 target,
440 syntax,
441 is_module,
442 comments,
443 )
444 }
445
446 #[allow(clippy::too_many_arguments)]
452 pub fn print<T>(&self, node: &T, args: PrintArgs) -> Result<TransformOutput, Error>
453 where
454 T: Node + VisitWith<swc_compiler_base::IdentCollector>,
455 {
456 swc_compiler_base::print(self.cm.clone(), node, args)
457 }
458}
459
460impl Compiler {
462 pub fn new(cm: Arc<SourceMap>) -> Self {
463 Compiler {
464 cm,
465 comments: Default::default(),
466 }
467 }
468
469 #[tracing::instrument(skip_all)]
470 pub fn read_config(&self, opts: &Options, name: &FileName) -> Result<Option<Config>, Error> {
471 static CUR_DIR: Lazy<PathBuf> = Lazy::new(|| {
472 if cfg!(target_arch = "wasm32") {
473 PathBuf::new()
474 } else {
475 ::std::env::current_dir().unwrap()
476 }
477 });
478
479 self.run(|| -> Result<_, Error> {
480 let Options {
481 ref root,
482 root_mode,
483 swcrc,
484 config_file,
485 ..
486 } = opts;
487
488 let root = root.as_ref().unwrap_or(&CUR_DIR);
489
490 let swcrc_path = match config_file {
491 Some(ConfigFile::Str(s)) => Some(PathBuf::from(s.clone())),
492 _ => {
493 if *swcrc {
494 if let FileName::Real(ref path) = name {
495 find_swcrc(path, root, *root_mode)
496 } else {
497 None
498 }
499 } else {
500 None
501 }
502 }
503 };
504
505 let config_file = match swcrc_path.as_deref() {
506 Some(s) => Some(load_swcrc(s)?),
507 _ => None,
508 };
509 let filename_path = match name {
510 FileName::Real(p) => Some(&**p),
511 _ => None,
512 };
513
514 if let Some(filename_path) = filename_path {
515 if let Some(config) = config_file {
516 let dir = swcrc_path
517 .as_deref()
518 .and_then(|p| p.parent())
519 .expect(".swcrc path should have parent dir");
520
521 let mut config = config
522 .into_config(Some(filename_path))
523 .context("failed to process config file")?;
524
525 if let Some(c) = &mut config {
526 if c.jsc.base_url != PathBuf::new() {
527 let joined = dir.join(&c.jsc.base_url);
528 c.jsc.base_url = if cfg!(target_os = "windows")
529 && c.jsc.base_url.as_os_str() == "."
530 {
531 dir.canonicalize().with_context(|| {
532 format!(
533 "failed to canonicalize base url using the path of \
534 .swcrc\nDir: {}\n(Used logic for windows)",
535 dir.display(),
536 )
537 })?
538 } else {
539 joined.canonicalize().with_context(|| {
540 format!(
541 "failed to canonicalize base url using the path of \
542 .swcrc\nPath: {}\nDir: {}\nbaseUrl: {}",
543 joined.display(),
544 dir.display(),
545 c.jsc.base_url.display()
546 )
547 })?
548 };
549 }
550 }
551
552 return Ok(config);
553 }
554
555 let config_file = config_file.unwrap_or_default();
556 let config = config_file.into_config(Some(filename_path))?;
557
558 return Ok(config);
559 }
560
561 let config = match config_file {
562 Some(config_file) => config_file.into_config(None)?,
563 None => Rc::default().into_config(None)?,
564 };
565
566 match config {
567 Some(config) => Ok(Some(config)),
568 None => {
569 bail!("no config matched for file ({})", name)
570 }
571 }
572 })
573 .with_context(|| format!("failed to read .swcrc file for input file at `{name}`"))
574 }
575
576 #[tracing::instrument(skip_all)]
582 pub fn parse_js_as_input<'a, P>(
583 &'a self,
584 fm: Lrc<SourceFile>,
585 program: Option<Program>,
586 handler: &'a Handler,
587 opts: &Options,
588 name: &FileName,
589 comments: Option<&'a SingleThreadedComments>,
590 before_pass: impl 'a + FnOnce(&Program) -> P,
591 ) -> Result<Option<BuiltInput<impl 'a + Pass>>, Error>
592 where
593 P: 'a + Pass,
594 {
595 self.run(move || {
596 let _timer = timer!("Compiler.parse");
597
598 if let FileName::Real(ref path) = name {
599 if !opts.config.matches(path)? {
600 return Ok(None);
601 }
602 }
603
604 let config = self.read_config(opts, name)?;
605 let config = match config {
606 Some(v) => v,
607 None => return Ok(None),
608 };
609
610 let built = opts.build_as_input(
611 &self.cm,
612 name,
613 move |syntax, target, is_module| match program {
614 Some(v) => Ok(v),
615 _ => self.parse_js(
616 fm.clone(),
617 handler,
618 target,
619 syntax,
620 is_module,
621 comments.as_ref().map(|v| v as _),
622 ),
623 },
624 opts.output_path.as_deref(),
625 opts.source_root.clone(),
626 opts.source_file_name.clone(),
627 config.source_map_ignore_list.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 program.fold_with(&mut pass)
657 })
658 }
659
660 #[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, 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, source_map_url) = 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, obj.url.as_deref()))
769 })
770 .unwrap_as_option(|v| {
771 Some(Ok(match v {
772 Some(true) => (SourceMapsConfig::Bool(true), None, None),
773 _ => (SourceMapsConfig::Bool(false), None, 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 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_ignore_list: opts.source_map_ignore_list.clone(),
906 source_map_names: &source_map_names,
907 orig,
908 comments: Some(&comments),
909 emit_source_map_columns: opts.emit_source_map_columns,
910 preamble: &opts.format.preamble,
911 codegen_config: swc_ecma_codegen::Config::default()
912 .with_target(target)
913 .with_minify(true)
914 .with_ascii_only(opts.format.ascii_only)
915 .with_emit_assert_for_import_attributes(
916 opts.format.emit_assert_for_import_attributes,
917 )
918 .with_inline_script(opts.format.inline_script)
919 .with_reduce_escaped_newline(
920 min_opts
921 .compress
922 .unwrap_or_default()
923 .experimental
924 .reduce_escaped_newline,
925 ),
926 output: None,
927 source_map_url,
928 },
929 );
930
931 ret.map(|mut output| {
932 output.diagnostics = handler.take_diagnostics();
933
934 output
935 })
936 })
937 }
938
939 #[tracing::instrument(skip_all)]
943 pub fn process_js(
944 &self,
945 handler: &Handler,
946 program: Program,
947 opts: &Options,
948 ) -> Result<TransformOutput, Error> {
949 let loc = self.cm.lookup_char_pos(program.span().lo());
950 let fm = loc.file;
951
952 self.process_js_with_custom_pass(
953 fm,
954 Some(program),
955 handler,
956 opts,
957 SingleThreadedComments::default(),
958 |_| noop_pass(),
959 |_| noop_pass(),
960 )
961 }
962
963 #[tracing::instrument(name = "swc::Compiler::apply_transforms", skip_all)]
964 fn apply_transforms(
965 &self,
966 handler: &Handler,
967 comments: SingleThreadedComments,
968 fm: Arc<SourceFile>,
969 orig: Option<sourcemap::SourceMap>,
970 config: BuiltInput<impl Pass>,
971 ) -> Result<TransformOutput, Error> {
972 self.run(|| {
973 let program = config.program;
974
975 if config.emit_isolated_dts && !config.syntax.typescript() {
976 handler.warn(
977 "jsc.experimental.emitIsolatedDts is enabled but the syntax is not TypeScript",
978 );
979 }
980
981 let emit_dts = config.syntax.typescript() && config.emit_isolated_dts;
982 let source_map_names = if config.source_maps.enabled() {
983 let mut v = swc_compiler_base::IdentCollector {
984 names: Default::default(),
985 };
986
987 program.visit_with(&mut v);
988
989 v.names
990 } else {
991 Default::default()
992 };
993
994 let dts_code = if emit_dts {
995 let (leading, trailing) = comments.borrow_all();
996
997 let leading = std::rc::Rc::new(RefCell::new(leading.clone()));
998 let trailing = std::rc::Rc::new(RefCell::new(trailing.clone()));
999
1000 let comments = SingleThreadedComments::from_leading_and_trailing(leading, trailing);
1001 let mut checker =
1002 FastDts::new(fm.name.clone(), config.unresolved_mark, Default::default());
1003 let mut program = program.clone();
1004
1005 if let Some((base, resolver)) = config.resolver {
1006 program.mutate(import_rewriter(base, resolver));
1007 }
1008
1009 let issues = checker.transform(&mut program);
1010
1011 for issue in issues {
1012 handler
1013 .struct_span_err(issue.range.span, &issue.message)
1014 .emit();
1015 }
1016
1017 let dts_code = to_code_with_comments(Some(&comments), &program);
1018 Some(dts_code)
1019 } else {
1020 None
1021 };
1022
1023 let pass = config.pass;
1024 let (program, output) = swc_transform_common::output::capture(|| {
1025 if let Some(dts_code) = dts_code {
1026 experimental_emit("__swc_isolated_declarations__".into(), dts_code);
1027 }
1028
1029 helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
1030 HANDLER.set(handler, || {
1031 program.apply(pass)
1033 })
1034 })
1035 });
1036
1037 if let Some(comments) = &config.comments {
1038 swc_compiler_base::minify_file_comments(
1039 comments,
1040 config.preserve_comments,
1041 config.output.preserve_annotations.into_bool(),
1042 );
1043 }
1044
1045 self.print(
1046 &program,
1047 PrintArgs {
1048 source_root: config.source_root.as_deref(),
1049 source_file_name: config.source_file_name.as_deref(),
1050 source_map_ignore_list: config.source_map_ignore_list.clone(),
1051 output_path: config.output_path,
1052 inline_sources_content: config.inline_sources_content,
1053 source_map: config.source_maps,
1054 source_map_names: &source_map_names,
1055 orig,
1056 comments: config.comments.as_ref().map(|v| v as _),
1057 emit_source_map_columns: config.emit_source_map_columns,
1058 preamble: &config.output.preamble,
1059 codegen_config: swc_ecma_codegen::Config::default()
1060 .with_target(config.target)
1061 .with_minify(config.minify)
1062 .with_ascii_only(
1063 config
1064 .output
1065 .charset
1066 .map(|v| matches!(v, OutputCharset::Ascii))
1067 .unwrap_or(false),
1068 )
1069 .with_emit_assert_for_import_attributes(
1070 config.emit_assert_for_import_attributes,
1071 )
1072 .with_inline_script(config.codegen_inline_script),
1073 output: if output.is_empty() {
1074 None
1075 } else {
1076 Some(output)
1077 },
1078 source_map_url: config.output.source_map_url.as_deref(),
1079 },
1080 )
1081 })
1082 }
1083}
1084
1085#[non_exhaustive]
1086#[derive(Clone, Default)]
1087pub struct JsMinifyExtras {
1088 pub mangle_name_cache: Option<Arc<dyn MangleCache>>,
1089}
1090
1091impl JsMinifyExtras {
1092 pub fn with_mangle_name_cache(
1093 mut self,
1094 mangle_name_cache: Option<Arc<dyn MangleCache>>,
1095 ) -> Self {
1096 self.mangle_name_cache = mangle_name_cache;
1097 self
1098 }
1099}
1100
1101fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option<PathBuf> {
1102 let mut parent = path.parent();
1103 while let Some(dir) = parent {
1104 let swcrc = dir.join(".swcrc");
1105
1106 if swcrc.exists() {
1107 return Some(swcrc);
1108 }
1109
1110 if dir == root && root_mode == RootMode::Root {
1111 break;
1112 }
1113 parent = dir.parent();
1114 }
1115
1116 None
1117}
1118
1119#[tracing::instrument(skip_all)]
1120fn load_swcrc(path: &Path) -> Result<Rc, Error> {
1121 let content = read_to_string(path).context("failed to read config (.swcrc) file")?;
1122
1123 parse_swcrc(&content)
1124}
1125
1126fn parse_swcrc(s: &str) -> Result<Rc, Error> {
1127 fn convert_json_err(e: serde_json::Error) -> Error {
1128 let line = e.line();
1129 let column = e.column();
1130
1131 let msg = match e.classify() {
1132 Category::Io => "io error",
1133 Category::Syntax => "syntax error",
1134 Category::Data => "unmatched data",
1135 Category::Eof => "unexpected eof",
1136 };
1137 Error::new(e).context(format!(
1138 "failed to deserialize .swcrc (json) file: {msg}: {line}:{column}"
1139 ))
1140 }
1141
1142 let v = parse_to_serde_value(
1143 s.trim_start_matches('\u{feff}'),
1144 &ParseOptions {
1145 allow_comments: true,
1146 allow_trailing_commas: true,
1147 allow_loose_object_property_names: false,
1148 },
1149 )?
1150 .ok_or_else(|| Error::msg("failed to deserialize empty .swcrc (json) file"))?;
1151
1152 if let Ok(rc) = serde_json::from_value(v.clone()) {
1153 return Ok(rc);
1154 }
1155
1156 serde_json::from_value(v)
1157 .map(Rc::Single)
1158 .map_err(convert_json_err)
1159}