Skip to content

Commit 1cf08c0

Browse files
committed
refactor(minifier): make DCE remove more code to align with rollup (#12427)
1 parent c375981 commit 1cf08c0

File tree

9 files changed

+376
-384
lines changed

9 files changed

+376
-384
lines changed

crates/oxc_minifier/examples/dce.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fn main() -> std::io::Result<()> {
4040
fn dce(allocator: &Allocator, source_text: &str, source_type: SourceType, nospace: bool) -> String {
4141
let ret = Parser::new(allocator, source_text, source_type).parse();
4242
let mut program = ret.program;
43-
Compressor::new(allocator).dead_code_elimination(&mut program, CompressOptions::smallest());
43+
Compressor::new(allocator).dead_code_elimination(&mut program, CompressOptions::dce());
4444
Codegen::new()
4545
.with_options(CodegenOptions { minify: nospace, ..CodegenOptions::default() })
4646
.build(&program)

crates/oxc_minifier/src/keep_var.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@ impl<'a> KeepVar<'a> {
5959
Self { ast, vars: std::vec![], all_hoisted: true }
6060
}
6161

62-
pub fn all_hoisted(&self) -> bool {
63-
self.all_hoisted
64-
}
65-
6662
pub fn get_variable_declaration(self) -> Option<ArenaBox<'a, VariableDeclaration<'a>>> {
6763
if self.vars.is_empty() {
6864
return None;

crates/oxc_minifier/src/options.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ pub struct CompressOptions {
2424
/// Default `false`
2525
pub drop_console: bool,
2626

27+
/// Join consecutive var, let and const statements.
28+
///
29+
/// Default `true`
30+
pub join_vars: bool,
31+
32+
/// Join consecutive simple statements using the comma operator.
33+
///
34+
/// `a; b` -> `a, b`
35+
///
36+
/// Default `true`
37+
pub sequences: bool,
38+
2739
/// Drop unreferenced functions and variables.
2840
pub unused: CompressOptionsUnused,
2941

@@ -48,6 +60,8 @@ impl CompressOptions {
4860
keep_names: CompressOptionsKeepNames::all_false(),
4961
drop_debugger: true,
5062
drop_console: false,
63+
join_vars: true,
64+
sequences: true,
5165
unused: CompressOptionsUnused::Remove,
5266
treeshake: TreeShakeOptions::default(),
5367
}
@@ -59,10 +73,25 @@ impl CompressOptions {
5973
keep_names: CompressOptionsKeepNames::all_true(),
6074
drop_debugger: false,
6175
drop_console: false,
76+
join_vars: true,
77+
sequences: true,
6278
unused: CompressOptionsUnused::Keep,
6379
treeshake: TreeShakeOptions::default(),
6480
}
6581
}
82+
83+
pub fn dce() -> Self {
84+
Self {
85+
target: ESTarget::ESNext,
86+
keep_names: CompressOptionsKeepNames::all_true(),
87+
drop_debugger: false,
88+
drop_console: false,
89+
join_vars: false,
90+
sequences: false,
91+
unused: CompressOptionsUnused::Remove,
92+
treeshake: TreeShakeOptions::default(),
93+
}
94+
}
6695
}
6796

6897
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]

crates/oxc_minifier/src/peephole/minimize_statements.rs

Lines changed: 247 additions & 217 deletions
Large diffs are not rendered by default.

crates/oxc_minifier/src/peephole/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,10 @@ impl<'a> Traverse<'a, MinifierState<'a>> for DeadCodeElimination {
483483
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
484484
let mut state = State::default();
485485
let mut ctx = Ctx::new(ctx);
486-
self.inner.remove_dead_code_exit_statements(stmts, &mut state, &mut ctx);
486+
self.inner.minimize_statements(stmts, &mut state, &mut ctx);
487+
if state.changed {
488+
self.inner.minimize_statements(stmts, &mut state, &mut ctx);
489+
}
487490
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
488491
}
489492

crates/oxc_minifier/src/peephole/remove_dead_code.rs

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -64,71 +64,6 @@ impl<'a> PeepholeOptimizations {
6464
}
6565
}
6666

67-
/// Removes dead code thats comes after `return`, `throw`, `continue` and `break` statements.
68-
pub fn remove_dead_code_exit_statements(
69-
&self,
70-
stmts: &mut Vec<'a, Statement<'a>>,
71-
state: &mut State,
72-
ctx: &mut Ctx<'a, '_>,
73-
) {
74-
// Remove code after `return` and `throw` statements
75-
let mut index = None;
76-
'outer: for (i, stmt) in stmts.iter().enumerate() {
77-
if stmt.is_jump_statement() {
78-
index.replace(i);
79-
break;
80-
}
81-
// Double check block statements folded by if statements above
82-
if let Statement::BlockStatement(block_stmt) = stmt {
83-
for stmt in &block_stmt.body {
84-
if stmt.is_jump_statement() {
85-
index.replace(i);
86-
break 'outer;
87-
}
88-
}
89-
}
90-
}
91-
92-
let Some(index) = index else { return };
93-
if index == stmts.len() - 1 {
94-
return;
95-
}
96-
97-
let mut keep_var = KeepVar::new(ctx.ast);
98-
99-
for stmt in stmts.iter().skip(index + 1) {
100-
keep_var.visit_statement(stmt);
101-
}
102-
103-
let mut i = 0;
104-
let len = stmts.len();
105-
stmts.retain(|s| {
106-
i += 1;
107-
if i - 1 <= index {
108-
return true;
109-
}
110-
// Keep module syntax and function declaration
111-
if s.is_module_declaration()
112-
|| matches!(s.as_declaration(), Some(Declaration::FunctionDeclaration(_)))
113-
{
114-
return true;
115-
}
116-
false
117-
});
118-
119-
let all_hoisted = keep_var.all_hoisted();
120-
if let Some(stmt) = keep_var.get_variable_declaration_statement() {
121-
stmts.push(stmt);
122-
if !all_hoisted {
123-
state.changed = true;
124-
}
125-
}
126-
127-
if stmts.len() != len {
128-
state.changed = true;
129-
}
130-
}
131-
13267
/// Remove block from single line blocks
13368
/// `{ block } -> block`
13469
fn try_optimize_block(

crates/oxc_minifier/tests/peephole/dead_code_elimination.rs

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ use oxc_minifier::Compressor;
77
use oxc_parser::Parser;
88
use oxc_span::SourceType;
99

10-
use super::default_options;
11-
1210
#[track_caller]
1311
fn run(source_text: &str, source_type: SourceType, options: Option<CompressOptions>) -> String {
1412
let allocator = Allocator::default();
@@ -28,7 +26,7 @@ fn test(source_text: &str, expected: &str) {
2826
let source_text = source_text.cow_replace("false", f);
2927

3028
let source_type = SourceType::default();
31-
let result = run(&source_text, source_type, Some(default_options()));
29+
let result = run(&source_text, source_type, Some(CompressOptions::dce()));
3230
let expected = run(expected, source_type, None);
3331
assert_eq!(result, expected, "\nfor source\n{source_text}\nexpect\n{expected}\ngot\n{result}");
3432
}
@@ -64,8 +62,8 @@ fn dce_if_statement() {
6462
"if (xxx) foo; else bar",
6563
);
6664
test(
67-
"if (xxx) { foo } else if (false) { var a; var b; } else if (false) { var c; var d; }",
68-
"if (xxx) foo; else if (0) var a, b; else if (0) var c, d;",
65+
"if (xxx) { foo } else if (false) { var a; var b; } else if (false) { var c; var d; } f(a,b,c,d)",
66+
"if (xxx) foo; else if (0) var a, b; else if (0) var c, d; f(a,b,c,d)",
6967
);
7068

7169
test("if (!false) { foo }", "foo");
@@ -88,18 +86,18 @@ fn dce_if_statement() {
8886
// Shadowed `undefined` as a variable should not be erased.
8987
// This is a rollup test.
9088
// <https://github.com/rollup/rollup/blob/master/test/function/samples/allow-undefined-as-parameter/main.js>
91-
test_same("function foo(undefined) { if (!undefined) throw Error('') }");
89+
test_same("function foo(undefined) { if (!undefined) throw Error('') } foo()");
9290

93-
test("function foo() { if (undefined) { bar } }", "function foo() { }");
94-
test("function foo() { { bar } }", "function foo() { bar }");
91+
test("function foo() { if (undefined) { bar } } foo()", "function foo() { } foo()");
92+
test("function foo() { { bar } } foo()", "function foo() { bar } foo()");
9593

9694
test("if (true) { foo; } if (true) { foo; }", "foo; foo;");
97-
test("if (true) { foo; return } foo; if (true) { bar; return } bar;", "{ foo; return }");
95+
test("if (true) { foo; return } foo; if (true) { bar; return } bar;", "foo; return");
9896

9997
// nested expression
10098
test(
101-
"const a = { fn: function() { if (true) { foo; } } }",
102-
"const a = { fn: function() { foo; } }",
99+
"const a = { fn: function() { if (true) { foo; } } } bar(a)",
100+
"const a = { fn: function() { foo; } } bar(a)",
103101
);
104102

105103
// parenthesized
@@ -128,61 +126,67 @@ fn dce_conditional_expression() {
128126
test("!!false ? foo : bar;", "bar");
129127
test("!!true ? foo : bar;", "foo");
130128

131-
test("const foo = true ? A : B", "const foo = A");
132-
test("const foo = false ? A : B", "const foo = B");
129+
test("const foo = true ? A : B", "A");
130+
test("const foo = false ? A : B", "B");
133131
}
134132

135133
#[test]
136134
fn dce_logical_expression() {
137135
test("false && bar()", "");
138136
test("true && bar()", "bar()");
139137

140-
test("const foo = false && bar()", "const foo = false");
141-
test("const foo = true && bar()", "const foo = bar()");
138+
test("var foo = false && bar(); baz(foo)", "var foo = false; baz(foo)");
139+
test("var foo = true && bar(); baz(foo)", "var foo = bar(); baz(foo)");
140+
141+
test("foo = false && bar()", "foo = false");
142+
test("foo = true && bar()", "foo = bar()");
142143
}
143144

144145
#[test]
145146
fn dce_var_hoisting() {
146147
test(
147148
"function f() {
149+
KEEP();
148150
return () => {
149151
var x;
150152
}
151153
REMOVE;
152-
function KEEP() {}
154+
function KEEP() { FOO }
153155
REMOVE;
154-
}",
156+
} f()",
155157
"function f() {
156-
return () => {
157-
var x;
158-
}
159-
function KEEP() {}
160-
}",
158+
KEEP();
159+
return () => { }
160+
function KEEP() { FOO }
161+
} f()",
161162
);
162163
test(
163164
"function f() {
165+
KEEP();
164166
return function g() {
165167
var x;
166168
}
167169
REMOVE;
168170
function KEEP() {}
169171
REMOVE;
170-
}",
172+
} f()",
171173
"function f() {
172-
return function g() {
173-
var x;
174-
}
174+
KEEP();
175+
return function g() {}
175176
function KEEP() {}
176-
}",
177+
} f()",
177178
);
178179
}
179180

180181
#[test]
181182
fn pure_comment_for_pure_global_constructors() {
182-
test("var x = new WeakSet", "var x = /* @__PURE__ */ new WeakSet();\n");
183-
test("var x = new WeakSet(null)", "var x = /* @__PURE__ */ new WeakSet(null);\n");
184-
test("var x = new WeakSet(undefined)", "var x = /* @__PURE__ */ new WeakSet(void 0);\n");
185-
test("var x = new WeakSet([])", "var x = /* @__PURE__ */ new WeakSet([]);\n");
183+
test("var x = new WeakSet; foo(x)", "var x = /* @__PURE__ */ new WeakSet();\nfoo(x)");
184+
test("var x = new WeakSet(null); foo(x)", "var x = /* @__PURE__ */ new WeakSet(null);\nfoo(x)");
185+
test(
186+
"var x = new WeakSet(undefined); foo(x)",
187+
"var x = /* @__PURE__ */ new WeakSet(void 0);\nfoo(x)",
188+
);
189+
test("var x = new WeakSet([]); foo(x)", "var x = /* @__PURE__ */ new WeakSet([]);\nfoo(x)");
186190
}
187191

188192
#[test]
@@ -203,13 +207,12 @@ fn dce_from_terser() {
203207
if (x) {
204208
y();
205209
}
206-
}",
210+
} f()",
207211
"function f() {
208212
a();
209213
b();
210214
x = 10;
211-
return;
212-
}",
215+
} f()",
213216
);
214217

215218
test(
@@ -248,9 +251,6 @@ fn dce_from_terser() {
248251
}
249252
console.log(foo, bar, Baz);
250253
",
251-
"
252-
if (0) var qux;
253-
console.log(foo, bar, Baz);
254-
",
254+
"console.log(foo, bar, Baz);",
255255
);
256256
}

0 commit comments

Comments
 (0)