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