1use std::sync::Arc;
2
3use anyhow::{Context, Result, bail};
4use bytes_str::BytesStr;
5use swc_core::{
6 base::try_with_handler,
7 common::{
8 BytePos, FileName, FilePathMapping, GLOBALS, LineCol, Mark, SourceMap as SwcSourceMap,
9 comments::{Comments, SingleThreadedComments},
10 },
11 ecma::{
12 self,
13 ast::{EsVersion, Program},
14 codegen::{
15 Emitter,
16 text_writer::{self, JsWriter, WriteJs},
17 },
18 minifier::option::{CompressOptions, ExtraOptions, MangleOptions, MinifyOptions},
19 parser::{Parser, StringInput, Syntax, lexer::Lexer},
20 transforms::base::{
21 fixer::paren_remover,
22 hygiene::{self, hygiene_with_config},
23 },
24 },
25};
26use tracing::{Level, instrument};
27use turbopack_core::{
28 chunk::MangleType,
29 code_builder::{Code, CodeBuilder},
30};
31
32use crate::parse::generate_js_source_map;
33
34#[instrument(level = Level::INFO, skip_all)]
35pub fn minify(code: Code, source_maps: bool, mangle: Option<MangleType>) -> Result<Code> {
36 let source_maps = source_maps
37 .then(|| code.generate_source_map_ref())
38 .transpose()?;
39
40 let source_code = BytesStr::from_utf8(code.into_source_code().into_bytes())?;
41
42 let cm = Arc::new(SwcSourceMap::new(FilePathMapping::empty()));
43 let (src, mut src_map_buf) = {
44 let fm = cm.new_source_file(FileName::Anon.into(), source_code);
45
46 let comments = SingleThreadedComments::default();
48
49 let lexer = Lexer::new(
50 Syntax::default(),
51 EsVersion::latest(),
52 StringInput::from(&*fm),
53 Some(&comments),
54 );
55 let mut parser = Parser::new_from(lexer);
56
57 let program = try_with_handler(cm.clone(), Default::default(), |handler| {
58 GLOBALS.set(&Default::default(), || {
59 let program = match parser.parse_program() {
60 Ok(program) => program,
61 Err(err) => {
62 err.into_diagnostic(handler).emit();
63 bail!("failed to parse source code\n{}", fm.src)
64 }
65 };
66 let unresolved_mark = Mark::new();
67 let top_level_mark = Mark::new();
68
69 let program = program.apply(paren_remover(Some(&comments)));
70
71 let mut program = program.apply(swc_core::ecma::transforms::base::resolver(
72 unresolved_mark,
73 top_level_mark,
74 false,
75 ));
76
77 program = swc_core::ecma::minifier::optimize(
78 program,
79 cm.clone(),
80 Some(&comments),
81 None,
82 &MinifyOptions {
83 compress: Some(CompressOptions {
84 passes: 2,
87 keep_classnames: mangle.is_none(),
88 keep_fnames: mangle.is_none(),
89 ..Default::default()
90 }),
91 mangle: mangle.map(|mangle| {
92 let reserved = vec!["AbortSignal".into()];
93 match mangle {
94 MangleType::OptimalSize => MangleOptions {
95 reserved,
96 ..Default::default()
97 },
98 MangleType::Deterministic => MangleOptions {
99 reserved,
100 disable_char_freq: true,
101 ..Default::default()
102 },
103 }
104 }),
105 ..Default::default()
106 },
107 &ExtraOptions {
108 top_level_mark,
109 unresolved_mark,
110 mangle_name_cache: None,
111 },
112 );
113
114 if mangle.is_none() {
115 program.mutate(hygiene_with_config(hygiene::Config {
116 top_level_mark,
117 ..Default::default()
118 }));
119 }
120
121 Ok(program.apply(ecma::transforms::base::fixer::fixer(Some(
122 &comments as &dyn Comments,
123 ))))
124 })
125 })
126 .map_err(|e| e.to_pretty_error())?;
127
128 print_program(cm.clone(), program, source_maps.is_some())?
129 };
130
131 let mut builder = CodeBuilder::new(source_maps.is_some());
132 if let Some(original_map) = source_maps.as_ref() {
133 src_map_buf.shrink_to_fit();
134 builder.push_source(
135 &src.into(),
136 Some(generate_js_source_map(
137 &*cm,
138 src_map_buf,
139 Some(original_map),
140 true,
141 false,
145 )?),
146 );
147 } else {
148 builder.push_source(&src.into(), None);
149 }
150 Ok(builder.build())
151}
152
153fn print_program(
155 cm: Arc<SwcSourceMap>,
156 program: Program,
157 source_maps: bool,
158) -> Result<(String, Vec<(BytePos, LineCol)>)> {
159 let mut src_map_buf = vec![];
160
161 let src = {
162 let mut buf = vec![];
163 {
164 let wr = Box::new(text_writer::omit_trailing_semi(Box::new(JsWriter::new(
165 cm.clone(),
166 "\n",
167 &mut buf,
168 source_maps.then_some(&mut src_map_buf),
169 )))) as Box<dyn WriteJs>;
170
171 let mut emitter = Emitter {
172 cfg: swc_core::ecma::codegen::Config::default().with_minify(true),
173 comments: None,
174 cm: cm.clone(),
175 wr,
176 };
177
178 emitter
179 .emit_program(&program)
180 .context("failed to emit module")?;
181 }
182 unsafe { String::from_utf8_unchecked(buf) }
185 };
186
187 Ok((src, src_map_buf))
188}