1use napi::Either;
2use napi_derive::napi;
3
4use oxc_compat::EngineTargets;
5
6#[napi(object)]
7pub struct TreeShakeOptions {
8 pub annotations: Option<bool>,
15
16 pub manual_pure_functions: Option<Vec<String>>,
21
22 #[napi(ts_type = "boolean | 'always'")]
26 pub property_read_side_effects: Option<Either<bool, String>>,
27
28 pub property_write_side_effects: Option<bool>,
35
36 pub unknown_global_side_effects: Option<bool>,
43
44 pub invalid_import_side_effects: Option<bool>,
51}
52
53impl TryFrom<&TreeShakeOptions> for oxc_minifier::TreeShakeOptions {
54 type Error = String;
55
56 fn try_from(o: &TreeShakeOptions) -> Result<Self, Self::Error> {
57 let default = oxc_minifier::TreeShakeOptions::default();
58 Ok(oxc_minifier::TreeShakeOptions {
59 annotations: o.annotations.unwrap_or(default.annotations),
60 manual_pure_functions: o
61 .manual_pure_functions
62 .clone()
63 .unwrap_or(default.manual_pure_functions),
64 property_read_side_effects: match &o.property_read_side_effects {
65 Some(Either::A(false)) => oxc_minifier::PropertyReadSideEffects::None,
66 Some(Either::A(true)) => oxc_minifier::PropertyReadSideEffects::All,
67 Some(Either::B(s)) if s == "always" => oxc_minifier::PropertyReadSideEffects::All,
68 Some(Either::B(s)) => {
69 return Err(format!(
70 "Invalid propertyReadSideEffects value: '{s}'. Expected 'always'."
71 ));
72 }
73 None => default.property_read_side_effects,
74 },
75 property_write_side_effects: o
76 .property_write_side_effects
77 .unwrap_or(default.property_write_side_effects),
78 unknown_global_side_effects: o
79 .unknown_global_side_effects
80 .unwrap_or(default.unknown_global_side_effects),
81 invalid_import_side_effects: o
82 .invalid_import_side_effects
83 .unwrap_or(default.invalid_import_side_effects),
84 })
85 }
86}
87
88#[napi(object)]
89pub struct CompressOptions {
90 pub target: Option<Either<String, Vec<String>>>,
103
104 pub drop_console: Option<bool>,
108
109 pub drop_debugger: Option<bool>,
113
114 #[napi(ts_type = "boolean | 'keep_assign'")]
119 pub unused: Option<Either<bool, String>>,
120
121 pub keep_names: Option<CompressOptionsKeepNames>,
123
124 pub join_vars: Option<bool>,
128
129 pub sequences: Option<bool>,
135
136 pub drop_labels: Option<Vec<String>>,
142
143 pub max_iterations: Option<u8>,
145
146 pub treeshake: Option<TreeShakeOptions>,
148}
149
150impl TryFrom<&CompressOptions> for oxc_minifier::CompressOptions {
151 type Error = String;
152 fn try_from(o: &CompressOptions) -> Result<Self, Self::Error> {
153 let default = oxc_minifier::CompressOptions::default();
154 Ok(oxc_minifier::CompressOptions {
155 target: match &o.target {
156 Some(Either::A(s)) => EngineTargets::from_target(s)?,
157 Some(Either::B(list)) => EngineTargets::from_target_list(list)?,
158 _ => default.target,
159 },
160 drop_console: o.drop_console.unwrap_or(default.drop_console),
161 drop_debugger: o.drop_debugger.unwrap_or(default.drop_debugger),
162 join_vars: o.join_vars.unwrap_or(true),
163 sequences: o.sequences.unwrap_or(true),
164 unused: match &o.unused {
165 Some(Either::A(true)) => oxc_minifier::CompressOptionsUnused::Remove,
166 Some(Either::A(false)) => oxc_minifier::CompressOptionsUnused::Keep,
167 Some(Either::B(s)) => match s.as_str() {
168 "keep_assign" => oxc_minifier::CompressOptionsUnused::KeepAssign,
169 _ => return Err(format!("Invalid unused option: `{s}`.")),
170 },
171 None => default.unused,
172 },
173 keep_names: o.keep_names.as_ref().map(Into::into).unwrap_or_default(),
174 treeshake: match &o.treeshake {
175 Some(ts) => oxc_minifier::TreeShakeOptions::try_from(ts)?,
176 None => oxc_minifier::TreeShakeOptions::default(),
177 },
178 drop_labels: o
179 .drop_labels
180 .as_ref()
181 .map(|labels| labels.iter().cloned().collect())
182 .unwrap_or_default(),
183 max_iterations: o.max_iterations,
184 })
185 }
186}
187
188#[napi(object)]
189pub struct CompressOptionsKeepNames {
190 pub function: bool,
196
197 pub class: bool,
203}
204
205impl From<&CompressOptionsKeepNames> for oxc_minifier::CompressOptionsKeepNames {
206 fn from(o: &CompressOptionsKeepNames) -> Self {
207 oxc_minifier::CompressOptionsKeepNames { function: o.function, class: o.class }
208 }
209}
210
211#[napi(object)]
212#[derive(Default)]
213pub struct MangleOptions {
214 pub toplevel: Option<bool>,
218
219 pub keep_names: Option<Either<bool, MangleOptionsKeepNames>>,
223
224 pub debug: Option<bool>,
226}
227
228impl From<&MangleOptions> for oxc_minifier::MangleOptions {
229 fn from(o: &MangleOptions) -> Self {
230 let default = oxc_minifier::MangleOptions::default();
231 Self {
232 top_level: o.toplevel,
233 keep_names: match &o.keep_names {
234 Some(Either::A(false)) => oxc_minifier::MangleOptionsKeepNames::all_false(),
235 Some(Either::A(true)) => oxc_minifier::MangleOptionsKeepNames::all_true(),
236 Some(Either::B(o)) => oxc_minifier::MangleOptionsKeepNames::from(o),
237 None => default.keep_names,
238 },
239 debug: o.debug.unwrap_or(default.debug),
240 }
241 }
242}
243
244#[napi(object)]
245pub struct MangleOptionsKeepNames {
246 pub function: bool,
250
251 pub class: bool,
255}
256
257impl From<&MangleOptionsKeepNames> for oxc_minifier::MangleOptionsKeepNames {
258 fn from(o: &MangleOptionsKeepNames) -> Self {
259 oxc_minifier::MangleOptionsKeepNames { function: o.function, class: o.class }
260 }
261}
262
263#[napi(string_enum = "lowercase")]
264pub enum LegalCommentsMode {
265 None,
267 Inline,
269 Eof,
271 External,
273}
274
275#[napi(object)]
276pub struct LegalCommentsLinked {
277 pub linked: String,
280}
281
282#[napi(object)]
283pub struct CodegenOptions {
284 pub remove_whitespace: Option<bool>,
288
289 #[napi(ts_type = "'none' | 'inline' | 'eof' | 'external' | { linked: string }")]
299 pub legal_comments: Option<Either<LegalCommentsMode, LegalCommentsLinked>>,
300}
301
302impl Default for CodegenOptions {
303 fn default() -> Self {
304 Self { remove_whitespace: Some(true), legal_comments: None }
305 }
306}
307
308impl CodegenOptions {
309 pub fn to_codegen_options(&self) -> Result<oxc_codegen::CodegenOptions, String> {
315 let mut opts = if self.remove_whitespace.unwrap_or(true) {
316 oxc_codegen::CodegenOptions::minify()
317 } else {
318 oxc_codegen::CodegenOptions { minify: false, ..oxc_codegen::CodegenOptions::minify() }
320 };
321
322 if let Some(legal) = &self.legal_comments {
323 opts.comments.legal = match legal {
324 Either::A(mode) => match mode {
325 LegalCommentsMode::None => oxc_codegen::LegalComment::None,
326 LegalCommentsMode::Inline => oxc_codegen::LegalComment::Inline,
327 LegalCommentsMode::Eof => oxc_codegen::LegalComment::Eof,
328 LegalCommentsMode::External => oxc_codegen::LegalComment::External,
329 },
330 Either::B(linked) => {
331 if linked.linked.is_empty() {
332 return Err("legalComments.linked must be a non-empty path.".into());
333 }
334 oxc_codegen::LegalComment::Linked(linked.linked.clone())
335 }
336 };
337 }
338
339 Ok(opts)
340 }
341}
342
343#[napi(object)]
344#[derive(Default)]
345pub struct MinifyOptions {
346 pub module: Option<bool>,
348
349 pub compress: Option<Either<bool, CompressOptions>>,
350
351 pub mangle: Option<Either<bool, MangleOptions>>,
352
353 pub codegen: Option<Either<bool, CodegenOptions>>,
354
355 pub sourcemap: Option<bool>,
356}
357
358impl TryFrom<&MinifyOptions> for oxc_minifier::MinifierOptions {
359 type Error = String;
360
361 fn try_from(o: &MinifyOptions) -> Result<Self, Self::Error> {
362 let compress = match &o.compress {
363 Some(Either::A(false)) => None,
364 None | Some(Either::A(true)) => Some(oxc_minifier::CompressOptions::default()),
365 Some(Either::B(o)) => Some(oxc_minifier::CompressOptions::try_from(o)?),
366 };
367 let mangle = match &o.mangle {
368 Some(Either::A(false)) => None,
369 None | Some(Either::A(true)) => Some(oxc_minifier::MangleOptions::default()),
370 Some(Either::B(o)) => Some(oxc_minifier::MangleOptions::from(o)),
371 };
372 Ok(oxc_minifier::MinifierOptions { compress, mangle })
373 }
374}