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;
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
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 {}",
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 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 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 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 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 #[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
461impl 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 #[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 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.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 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 #[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 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}