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