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