Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d32678a

Browse files
committedMar 19, 2025··
feat(mangler): support keep_names option
1 parent 95f70a0 commit d32678a

File tree

8 files changed

+301
-52
lines changed

8 files changed

+301
-52
lines changed
 

‎crates/oxc_mangler/src/keep_names.rs

+157-34
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,75 @@ use oxc_ast::{AstKind, ast::*};
33
use oxc_semantic::{AstNode, AstNodes, ReferenceId, Scoping, SymbolId};
44
use rustc_hash::FxHashSet;
55

6-
#[cfg_attr(not(test), expect(dead_code))]
7-
pub fn collect_name_symbols(scoping: &Scoping, ast_nodes: &AstNodes) -> FxHashSet<SymbolId> {
8-
let collector = NameSymbolCollector::new(scoping, ast_nodes);
6+
#[derive(Debug, Clone, Copy, Default)]
7+
pub struct MangleOptionsKeepNames {
8+
/// Keep function names so that `Function.prototype.name` is preserved.
9+
///
10+
/// Default `false`
11+
pub function: bool,
12+
13+
/// Keep class names so that `Class.prototype.name` is preserved.
14+
///
15+
/// Default `false`
16+
pub class: bool,
17+
}
18+
19+
impl MangleOptionsKeepNames {
20+
pub fn all_false() -> Self {
21+
Self { function: false, class: false }
22+
}
23+
24+
pub fn all_true() -> Self {
25+
Self { function: true, class: true }
26+
}
27+
28+
#[cfg(test)]
29+
pub(crate) fn function_only() -> Self {
30+
Self { function: true, class: false }
31+
}
32+
33+
#[cfg(test)]
34+
pub(crate) fn class_only() -> Self {
35+
Self { function: false, class: true }
36+
}
37+
}
38+
39+
impl From<bool> for MangleOptionsKeepNames {
40+
fn from(keep_names: bool) -> Self {
41+
if keep_names { Self::all_true() } else { Self::all_false() }
42+
}
43+
}
44+
45+
pub fn collect_name_symbols(
46+
options: MangleOptionsKeepNames,
47+
scoping: &Scoping,
48+
ast_nodes: &AstNodes,
49+
) -> FxHashSet<SymbolId> {
50+
let collector = NameSymbolCollector::new(options, scoping, ast_nodes);
951
collector.collect()
1052
}
1153

1254
/// Collects symbols that are used to set `name` properties of functions and classes.
1355
struct NameSymbolCollector<'a, 'b> {
56+
options: MangleOptionsKeepNames,
1457
scoping: &'b Scoping,
1558
ast_nodes: &'b AstNodes<'a>,
1659
}
1760

1861
impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
19-
fn new(scoping: &'b Scoping, ast_nodes: &'b AstNodes<'a>) -> Self {
20-
Self { scoping, ast_nodes }
62+
fn new(
63+
options: MangleOptionsKeepNames,
64+
scoping: &'b Scoping,
65+
ast_nodes: &'b AstNodes<'a>,
66+
) -> Self {
67+
Self { options, scoping, ast_nodes }
2168
}
2269

2370
fn collect(self) -> FxHashSet<SymbolId> {
71+
if !self.options.function && !self.options.class {
72+
return FxHashSet::default();
73+
}
74+
2475
self.scoping
2576
.symbol_ids()
2677
.filter(|symbol_id| {
@@ -42,9 +93,12 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
4293
fn is_name_set_declare_node(&self, node: &'a AstNode, symbol_id: SymbolId) -> bool {
4394
match node.kind() {
4495
AstKind::Function(function) => {
45-
function.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
96+
self.options.function
97+
&& function.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
98+
}
99+
AstKind::Class(cls) => {
100+
self.options.class && cls.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
46101
}
47-
AstKind::Class(cls) => cls.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id),
48102
AstKind::VariableDeclarator(decl) => {
49103
if let BindingPatternKind::BindingIdentifier(id) = &decl.id.kind {
50104
if id.symbol_id() == symbol_id {
@@ -176,9 +230,18 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
176230
}
177231
}
178232

179-
#[expect(clippy::unused_self)]
180233
fn is_expression_whose_name_needs_to_be_kept(&self, expr: &Expression) -> bool {
181-
expr.is_anonymous_function_definition()
234+
let is_anonymous = expr.is_anonymous_function_definition();
235+
if !is_anonymous {
236+
return false;
237+
}
238+
239+
if self.options.class && self.options.function {
240+
return true;
241+
}
242+
243+
let is_class = matches!(expr, Expression::ClassExpression(_));
244+
(self.options.class && is_class) || (self.options.function && !is_class)
182245
}
183246
}
184247

@@ -191,17 +254,17 @@ mod test {
191254
use rustc_hash::FxHashSet;
192255
use std::iter::once;
193256

194-
use super::collect_name_symbols;
257+
use super::{MangleOptionsKeepNames, collect_name_symbols};
195258

196-
fn collect(source_text: &str) -> FxHashSet<String> {
259+
fn collect(opts: MangleOptionsKeepNames, source_text: &str) -> FxHashSet<String> {
197260
let allocator = Allocator::default();
198261
let ret = Parser::new(&allocator, source_text, SourceType::mjs()).parse();
199262
assert!(!ret.panicked, "{source_text}");
200263
assert!(ret.errors.is_empty(), "{source_text}");
201264
let ret = SemanticBuilder::new().build(&ret.program);
202265
assert!(ret.errors.is_empty(), "{source_text}");
203266
let semantic = ret.semantic;
204-
let symbols = collect_name_symbols(semantic.scoping(), semantic.nodes());
267+
let symbols = collect_name_symbols(opts, semantic.scoping(), semantic.nodes());
205268
symbols
206269
.into_iter()
207270
.map(|symbol_id| semantic.scoping().symbol_name(symbol_id).to_string())
@@ -210,60 +273,120 @@ mod test {
210273

211274
#[test]
212275
fn test_declarations() {
213-
assert_eq!(collect("function foo() {}"), once("foo".to_string()).collect());
214-
assert_eq!(collect("class Foo {}"), once("Foo".to_string()).collect());
276+
assert_eq!(
277+
collect(MangleOptionsKeepNames::function_only(), "function foo() {}"),
278+
once("foo".to_string()).collect()
279+
);
280+
assert_eq!(
281+
collect(MangleOptionsKeepNames::class_only(), "class Foo {}"),
282+
once("Foo".to_string()).collect()
283+
);
215284
}
216285

217286
#[test]
218287
fn test_simple_declare_init() {
219-
assert_eq!(collect("var foo = function() {}"), once("foo".to_string()).collect());
220-
assert_eq!(collect("var foo = () => {}"), once("foo".to_string()).collect());
221-
assert_eq!(collect("var Foo = class {}"), once("Foo".to_string()).collect());
288+
assert_eq!(
289+
collect(MangleOptionsKeepNames::function_only(), "var foo = function() {}"),
290+
once("foo".to_string()).collect()
291+
);
292+
assert_eq!(
293+
collect(MangleOptionsKeepNames::function_only(), "var foo = () => {}"),
294+
once("foo".to_string()).collect()
295+
);
296+
assert_eq!(
297+
collect(MangleOptionsKeepNames::class_only(), "var Foo = class {}"),
298+
once("Foo".to_string()).collect()
299+
);
222300
}
223301

224302
#[test]
225303
fn test_simple_assign() {
226-
assert_eq!(collect("var foo; foo = function() {}"), once("foo".to_string()).collect());
227-
assert_eq!(collect("var foo; foo = () => {}"), once("foo".to_string()).collect());
228-
assert_eq!(collect("var Foo; Foo = class {}"), once("Foo".to_string()).collect());
304+
assert_eq!(
305+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo = function() {}"),
306+
once("foo".to_string()).collect()
307+
);
308+
assert_eq!(
309+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo = () => {}"),
310+
once("foo".to_string()).collect()
311+
);
312+
assert_eq!(
313+
collect(MangleOptionsKeepNames::class_only(), "var Foo; Foo = class {}"),
314+
once("Foo".to_string()).collect()
315+
);
229316

230-
assert_eq!(collect("var foo; foo ||= function() {}"), once("foo".to_string()).collect());
231317
assert_eq!(
232-
collect("var foo = 1; foo &&= function() {}"),
318+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo ||= function() {}"),
319+
once("foo".to_string()).collect()
320+
);
321+
assert_eq!(
322+
collect(MangleOptionsKeepNames::function_only(), "var foo = 1; foo &&= function() {}"),
323+
once("foo".to_string()).collect()
324+
);
325+
assert_eq!(
326+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo ??= function() {}"),
233327
once("foo".to_string()).collect()
234328
);
235-
assert_eq!(collect("var foo; foo ??= function() {}"), once("foo".to_string()).collect());
236329
}
237330

238331
#[test]
239332
fn test_default_declarations() {
240-
assert_eq!(collect("var [foo = function() {}] = []"), once("foo".to_string()).collect());
241-
assert_eq!(collect("var [foo = () => {}] = []"), once("foo".to_string()).collect());
242-
assert_eq!(collect("var [Foo = class {}] = []"), once("Foo".to_string()).collect());
243-
assert_eq!(collect("var { foo = function() {} } = {}"), once("foo".to_string()).collect());
333+
assert_eq!(
334+
collect(MangleOptionsKeepNames::function_only(), "var [foo = function() {}] = []"),
335+
once("foo".to_string()).collect()
336+
);
337+
assert_eq!(
338+
collect(MangleOptionsKeepNames::function_only(), "var [foo = () => {}] = []"),
339+
once("foo".to_string()).collect()
340+
);
341+
assert_eq!(
342+
collect(MangleOptionsKeepNames::class_only(), "var [Foo = class {}] = []"),
343+
once("Foo".to_string()).collect()
344+
);
345+
assert_eq!(
346+
collect(MangleOptionsKeepNames::function_only(), "var { foo = function() {} } = {}"),
347+
once("foo".to_string()).collect()
348+
);
244349
}
245350

246351
#[test]
247352
fn test_default_assign() {
248353
assert_eq!(
249-
collect("var foo; [foo = function() {}] = []"),
354+
collect(MangleOptionsKeepNames::function_only(), "var foo; [foo = function() {}] = []"),
250355
once("foo".to_string()).collect()
251356
);
252-
assert_eq!(collect("var foo; [foo = () => {}] = []"), once("foo".to_string()).collect());
253-
assert_eq!(collect("var Foo; [Foo = class {}] = []"), once("Foo".to_string()).collect());
254357
assert_eq!(
255-
collect("var foo; ({ foo = function() {} } = {})"),
358+
collect(MangleOptionsKeepNames::function_only(), "var foo; [foo = () => {}] = []"),
359+
once("foo".to_string()).collect()
360+
);
361+
assert_eq!(
362+
collect(MangleOptionsKeepNames::class_only(), "var Foo; [Foo = class {}] = []"),
363+
once("Foo".to_string()).collect()
364+
);
365+
assert_eq!(
366+
collect(
367+
MangleOptionsKeepNames::function_only(),
368+
"var foo; ({ foo = function() {} } = {})"
369+
),
256370
once("foo".to_string()).collect()
257371
);
258372
}
259373

260374
#[test]
261375
fn test_for_in_declaration() {
262376
assert_eq!(
263-
collect("for (var foo = function() {} in []) {}"),
377+
collect(
378+
MangleOptionsKeepNames::function_only(),
379+
"for (var foo = function() {} in []) {}"
380+
),
381+
once("foo".to_string()).collect()
382+
);
383+
assert_eq!(
384+
collect(MangleOptionsKeepNames::function_only(), "for (var foo = () => {} in []) {}"),
264385
once("foo".to_string()).collect()
265386
);
266-
assert_eq!(collect("for (var foo = () => {} in []) {}"), once("foo".to_string()).collect());
267-
assert_eq!(collect("for (var Foo = class {} in []) {}"), once("Foo".to_string()).collect());
387+
assert_eq!(
388+
collect(MangleOptionsKeepNames::class_only(), "for (var Foo = class {} in []) {}"),
389+
once("Foo".to_string()).collect()
390+
);
268391
}
269392
}

‎crates/oxc_mangler/src/lib.rs

+37-7
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,32 @@ use std::iter;
22

33
use fixedbitset::FixedBitSet;
44
use itertools::Itertools;
5+
use keep_names::collect_name_symbols;
56
use rustc_hash::FxHashSet;
67

78
use base54::base54;
89
use oxc_allocator::{Allocator, Vec};
910
use oxc_ast::ast::{Declaration, Program, Statement};
1011
use oxc_data_structures::inline_string::InlineString;
1112
use oxc_index::Idx;
12-
use oxc_semantic::{Scoping, Semantic, SemanticBuilder, SymbolId};
13+
use oxc_semantic::{AstNodes, Scoping, Semantic, SemanticBuilder, SymbolId};
1314
use oxc_span::Atom;
1415

1516
pub(crate) mod base54;
1617
mod keep_names;
1718

19+
pub use keep_names::MangleOptionsKeepNames;
20+
1821
#[derive(Default, Debug, Clone, Copy)]
1922
pub struct MangleOptions {
2023
/// Pass true to mangle names declared in the top level scope.
2124
///
2225
/// Default: `false`
2326
pub top_level: bool,
2427

28+
/// Keep function / class names
29+
pub keep_names: MangleOptionsKeepNames,
30+
2531
/// Use more readable mangled names
2632
/// (e.g. `slot_0`, `slot_1`, `slot_2`, ...) for debugging.
2733
///
@@ -207,6 +213,8 @@ impl Mangler {
207213
} else {
208214
Default::default()
209215
};
216+
let (keep_name_names, keep_name_symbols) =
217+
Mangler::collect_keep_name_symbols(self.options.keep_names, &scoping, &ast_nodes);
210218

211219
let allocator = Allocator::default();
212220

@@ -226,6 +234,16 @@ impl Mangler {
226234
continue;
227235
}
228236

237+
// Sort `bindings` in declaration order.
238+
tmp_bindings.clear();
239+
tmp_bindings.extend(
240+
bindings.values().copied().filter(|binding| !keep_name_symbols.contains(binding)),
241+
);
242+
tmp_bindings.sort_unstable();
243+
if tmp_bindings.is_empty() {
244+
continue;
245+
}
246+
229247
let mut slot = slot_liveness.len();
230248

231249
reusable_slots.clear();
@@ -236,11 +254,11 @@ impl Mangler {
236254
.enumerate()
237255
.filter(|(_, slot_liveness)| !slot_liveness.contains(scope_id.index()))
238256
.map(|(slot, _)| slot)
239-
.take(bindings.len()),
257+
.take(tmp_bindings.len()),
240258
);
241259

242260
// The number of new slots that needs to be allocated.
243-
let remaining_count = bindings.len() - reusable_slots.len();
261+
let remaining_count = tmp_bindings.len() - reusable_slots.len();
244262
reusable_slots.extend(slot..slot + remaining_count);
245263

246264
slot += remaining_count;
@@ -249,10 +267,6 @@ impl Mangler {
249267
.resize_with(slot, || FixedBitSet::with_capacity(scoping.scopes_len()));
250268
}
251269

252-
// Sort `bindings` in declaration order.
253-
tmp_bindings.clear();
254-
tmp_bindings.extend(bindings.values().copied());
255-
tmp_bindings.sort_unstable();
256270
for (&symbol_id, assigned_slot) in
257271
tmp_bindings.iter().zip(reusable_slots.iter().copied())
258272
{
@@ -283,6 +297,7 @@ impl Mangler {
283297
let frequencies = self.tally_slot_frequencies(
284298
&scoping,
285299
&exported_symbols,
300+
&keep_name_symbols,
286301
total_number_of_slots,
287302
&slots,
288303
&allocator,
@@ -305,6 +320,8 @@ impl Mangler {
305320
&& !root_unresolved_references.contains_key(n)
306321
&& !(root_bindings.contains_key(n)
307322
&& (!self.options.top_level || exported_names.contains(n)))
323+
// TODO: only skip the names that are kept in the current scope
324+
&& !keep_name_names.contains(n)
308325
{
309326
break name;
310327
}
@@ -369,6 +386,7 @@ impl Mangler {
369386
&'a self,
370387
scoping: &Scoping,
371388
exported_symbols: &FxHashSet<SymbolId>,
389+
keep_name_symbols: &FxHashSet<SymbolId>,
372390
total_number_of_slots: usize,
373391
slots: &[Slot],
374392
allocator: &'a Allocator,
@@ -389,6 +407,9 @@ impl Mangler {
389407
if is_special_name(scoping.symbol_name(symbol_id)) {
390408
continue;
391409
}
410+
if keep_name_symbols.contains(&symbol_id) {
411+
continue;
412+
}
392413
let index = slot;
393414
frequencies[index].slot = slot;
394415
frequencies[index].frequency += scoping.get_resolved_reference_ids(symbol_id).len();
@@ -422,6 +443,15 @@ impl Mangler {
422443
.map(|id| (id.name, id.symbol_id()))
423444
.collect()
424445
}
446+
447+
fn collect_keep_name_symbols<'a>(
448+
keep_names: MangleOptionsKeepNames,
449+
scoping: &'a Scoping,
450+
nodes: &AstNodes,
451+
) -> (FxHashSet<&'a str>, FxHashSet<SymbolId>) {
452+
let ids = collect_name_symbols(keep_names, scoping, nodes);
453+
(ids.iter().map(|id| scoping.symbol_name(*id)).collect(), ids)
454+
}
425455
}
426456

427457
fn is_special_name(name: &str) -> bool {

‎crates/oxc_minifier/examples/mangler.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use pico_args::Arguments;
1515
fn main() -> std::io::Result<()> {
1616
let mut args = Arguments::from_env();
1717

18+
let keep_names = args.contains("--keep-names");
1819
let debug = args.contains("--debug");
1920
let twice = args.contains("--twice");
2021
let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string());
@@ -23,23 +24,27 @@ fn main() -> std::io::Result<()> {
2324
let source_text = std::fs::read_to_string(path)?;
2425
let source_type = SourceType::from_path(path).unwrap();
2526

26-
let printed = mangler(&source_text, source_type, debug);
27+
let printed = mangler(&source_text, source_type, keep_names, debug);
2728
println!("{printed}");
2829

2930
if twice {
30-
let printed2 = mangler(&printed, source_type, debug);
31+
let printed2 = mangler(&printed, source_type, keep_names, debug);
3132
println!("{printed2}");
3233
println!("same = {}", printed == printed2);
3334
}
3435

3536
Ok(())
3637
}
3738

38-
fn mangler(source_text: &str, source_type: SourceType, debug: bool) -> String {
39+
fn mangler(source_text: &str, source_type: SourceType, keep_names: bool, debug: bool) -> String {
3940
let allocator = Allocator::default();
4041
let ret = Parser::new(&allocator, source_text, source_type).parse();
4142
let symbol_table = Mangler::new()
42-
.with_options(MangleOptions { debug, top_level: source_type.is_module() })
43+
.with_options(MangleOptions {
44+
keep_names: keep_names.into(),
45+
debug,
46+
top_level: source_type.is_module(),
47+
})
4348
.build(&ret.program);
4449
CodeGenerator::new().with_scoping(Some(symbol_table)).build(&ret.program).code
4550
}

‎crates/oxc_minifier/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use oxc_ast::ast::Program;
1616
use oxc_mangler::Mangler;
1717
use oxc_semantic::{Scoping, SemanticBuilder, Stats};
1818

19-
pub use oxc_mangler::MangleOptions;
19+
pub use oxc_mangler::{MangleOptions, MangleOptionsKeepNames};
2020

2121
pub use crate::{
2222
compressor::Compressor, options::CompressOptions, options::CompressOptionsKeepNames,

‎crates/oxc_minifier/tests/mangler/mod.rs

+18-6
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ use oxc_mangler::{MangleOptions, Mangler};
66
use oxc_parser::Parser;
77
use oxc_span::SourceType;
88

9-
fn mangle(source_text: &str, top_level: bool) -> String {
9+
fn mangle(source_text: &str, top_level: bool, keep_names: bool) -> String {
1010
let allocator = Allocator::default();
1111
let source_type = SourceType::mjs();
1212
let ret = Parser::new(&allocator, source_text, source_type).parse();
1313
let program = ret.program;
14-
let symbol_table =
15-
Mangler::new().with_options(MangleOptions { debug: false, top_level }).build(&program);
14+
let symbol_table = Mangler::new()
15+
.with_options(MangleOptions { keep_names: keep_names.into(), debug: false, top_level })
16+
.build(&program);
1617
CodeGenerator::new().with_scoping(Some(symbol_table)).build(&program).code
1718
}
1819

1920
#[test]
2021
fn direct_eval() {
2122
let source_text = "function foo() { let NO_MANGLE; eval('') }";
22-
let mangled = mangle(source_text, false);
23+
let mangled = mangle(source_text, false, false);
2324
assert_eq!(mangled, "function foo() {\n\tlet NO_MANGLE;\n\teval(\"\");\n}\n");
2425
}
2526

@@ -61,14 +62,25 @@ fn mangler() {
6162
"export const foo = 1; foo",
6263
"const foo = 1; foo; export { foo }",
6364
];
65+
let keep_name_cases = [
66+
"function _() { function foo() { var x } }",
67+
"function _() { var foo = function() { var x } }",
68+
"function _() { var foo = () => { var x } }",
69+
"function _() { class Foo { foo() { var x } } }",
70+
"function _() { var Foo = class { foo() { var x } } }",
71+
];
6472

6573
let mut snapshot = String::new();
6674
cases.into_iter().fold(&mut snapshot, |w, case| {
67-
write!(w, "{case}\n{}\n", mangle(case, false)).unwrap();
75+
write!(w, "{case}\n{}\n", mangle(case, false, false)).unwrap();
6876
w
6977
});
7078
top_level_cases.into_iter().fold(&mut snapshot, |w, case| {
71-
write!(w, "{case}\n{}\n", mangle(case, true)).unwrap();
79+
write!(w, "{case}\n{}\n", mangle(case, true, false)).unwrap();
80+
w
81+
});
82+
keep_name_cases.into_iter().fold(&mut snapshot, |w, case| {
83+
write!(w, "{case}\n{}\n", mangle(case, false, true)).unwrap();
7284
w
7385
});
7486

‎crates/oxc_minifier/tests/mangler/snapshots/mangler.snap

+39
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,42 @@ const foo = 1; foo; export { foo }
235235
const e = 1;
236236
e;
237237
export { e as foo };
238+
239+
function _() { function foo() { var x } }
240+
function _() {
241+
function foo() {
242+
var e;
243+
}
244+
}
245+
246+
function _() { var foo = function() { var x } }
247+
function _() {
248+
var foo = function() {
249+
var e;
250+
};
251+
}
252+
253+
function _() { var foo = () => { var x } }
254+
function _() {
255+
var foo = () => {
256+
var e;
257+
};
258+
}
259+
260+
function _() { class Foo { foo() { var x } } }
261+
function _() {
262+
class Foo {
263+
foo() {
264+
var e;
265+
}
266+
}
267+
}
268+
269+
function _() { var Foo = class { foo() { var x } } }
270+
function _() {
271+
var Foo = class {
272+
foo() {
273+
var e;
274+
}
275+
};
276+
}

‎napi/minify/index.d.ts

+17
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,27 @@ export interface MangleOptions {
6565
* @default false
6666
*/
6767
toplevel?: boolean
68+
/** Keep function / class names */
69+
keepNames?: MangleOptionsKeepNames
6870
/** Debug mangled names. */
6971
debug?: boolean
7072
}
7173

74+
export interface MangleOptionsKeepNames {
75+
/**
76+
* Keep function names so that `Function.prototype.name` is preserved.
77+
*
78+
* @default false
79+
*/
80+
function: boolean
81+
/**
82+
* Keep class names so that `Class.prototype.name` is preserved.
83+
*
84+
* @default false
85+
*/
86+
class: boolean
87+
}
88+
7289
/** Minify synchronously. */
7390
export declare function minify(filename: string, sourceText: string, options?: MinifyOptions | undefined | null): MinifyResult
7491

‎napi/minify/src/options.rs

+23
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ pub struct MangleOptions {
9292
/// @default false
9393
pub toplevel: Option<bool>,
9494

95+
/// Keep function / class names
96+
pub keep_names: Option<MangleOptionsKeepNames>,
97+
9598
/// Debug mangled names.
9699
pub debug: Option<bool>,
97100
}
@@ -101,11 +104,31 @@ impl From<&MangleOptions> for oxc_minifier::MangleOptions {
101104
let default = oxc_minifier::MangleOptions::default();
102105
Self {
103106
top_level: o.toplevel.unwrap_or(default.top_level),
107+
keep_names: o.keep_names.as_ref().map(Into::into).unwrap_or_default(),
104108
debug: o.debug.unwrap_or(default.debug),
105109
}
106110
}
107111
}
108112

113+
#[napi(object)]
114+
pub struct MangleOptionsKeepNames {
115+
/// Keep function names so that `Function.prototype.name` is preserved.
116+
///
117+
/// @default false
118+
pub function: bool,
119+
120+
/// Keep class names so that `Class.prototype.name` is preserved.
121+
///
122+
/// @default false
123+
pub class: bool,
124+
}
125+
126+
impl From<&MangleOptionsKeepNames> for oxc_minifier::MangleOptionsKeepNames {
127+
fn from(o: &MangleOptionsKeepNames) -> Self {
128+
oxc_minifier::MangleOptionsKeepNames { function: o.function, class: o.class }
129+
}
130+
}
131+
109132
#[napi(object)]
110133
pub struct CodegenOptions {
111134
/// Remove whitespace.

0 commit comments

Comments
 (0)
Please sign in to comment.