Skip to content

Commit 1f6daec

Browse files
authored
feat: support tree shaking class members (#42)
1 parent 39ac0de commit 1f6daec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1067
-426
lines changed

README.md

+51-3
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,47 @@ export function main() {
121121

122122
</td></tr></tbody></table>
123123

124+
### Class Tree Shaking
125+
126+
> One of the hardest but the coolest.
127+
128+
<table><tbody><tr><td width="500px"> Input </td><td width="500px"> Output </td></tr><tr>
129+
<td valign="top">
130+
131+
```js
132+
class A {
133+
method(x) {
134+
console.log("A", x);
135+
}
136+
static static_prop = unknown;
137+
}
138+
class B extends A {
139+
method(x) {
140+
console.log("B", x);
141+
}
142+
unused() {
143+
console.log("unused");
144+
}
145+
}
146+
new B().method(A.static_prop);
147+
```
148+
149+
</td><td valign="top">
150+
151+
```js
152+
class A {
153+
static a = unknown;
154+
}
155+
class B extends A {
156+
a(x) {
157+
console.log("B", x);
158+
}
159+
}
160+
new B().a(A.a);
161+
```
162+
163+
</td></tr></tbody></table>
164+
124165
### JSX
125166

126167
> `createElement` also works, if it is directly imported from `react`.
@@ -204,9 +245,16 @@ export function main() {
204245

205246
## Comparison
206247

207-
- **Rollup**: Rollup tree-shakes the code in a multi-module context, while this project is focused on a single module. For some cases, this project can remove 10% more code than Rollup.
208-
- **Closure Compiler**: Closure Compiler can be considered as a tree shaker + minifier, while this project is only a tree shaker (for the minifier, we have `oxc_minifier`). Theoretically, we can shake more than Closure Compiler, but we cannot compare them directly because we don't have a equivalent minifier. Also, it's written in Java, which is hard to be integrated into the JS ecosystem.
209-
- **swc**: swc can also be considered as a tree shaker + minifier. TBH, currently swc is much faster and more complete. It is rule-based, which is a different approach from this project. It's also not compatible with the Oxc project, thus a new tree shaker is needed.
248+
- **Rollup**: Rollup tree-shakes the code in a multi-module context, and it has information about the side effects of the modules. This project does a more fine-grained tree-shaking, and it can be used as a post-processor for Rollup, and is expected to produce smaller code.
249+
- **Closure Compiler**/**swc**: they support both minification and dead code elimination, while this project is focused on tree-shaking (difference below). You can expect a size reduction when using tree-shaker on their output, and vice versa.
250+
251+
### What's Tree Shaking?
252+
253+
Here is a simple comparison:
254+
255+
- Minification: Removing whitespace, renaming variables, syntax-level optimizations, etc.
256+
- Dead code elimination: Removing code that is never executed, by using a set of rules, for example, "`if(false) { ... }` can be removed".
257+
- Tree shaking: Removing code that is never executed, by simulating the runtime behavior of the code. For example, "`if (x) { ... }` can only be preserved if `...` is reachable and has side effects".
210258

211259
## Todo
212260

crates/tree_shaker/src/builtins/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::{
1111
TreeShakeConfig,
1212
};
1313
use known_modules::KnownModule;
14+
pub use prototypes::BuiltinPrototype;
1415
use prototypes::BuiltinPrototypes;
15-
pub use prototypes::Prototype;
1616
use react::AnalyzerDataForReact;
1717
use rustc_hash::FxHashMap;
1818

crates/tree_shaker/src/builtins/prototypes/array.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_array_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_array_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("Array", create_object_prototype(factory), {
66
"at" => factory.immutable_unknown,
77
"concat" => factory.immutable_unknown /*pure_fn_returns_array*/,
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::entity::EntityFactory;
22

3-
use super::{object::create_object_prototype, Prototype};
3+
use super::{object::create_object_prototype, BuiltinPrototype};
44

5-
pub fn create_bigint_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
5+
pub fn create_bigint_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
66
create_object_prototype(factory).with_name("BigInt")
77
}

crates/tree_shaker/src/builtins/prototypes/boolean.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_boolean_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_boolean_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("Boolean", create_object_prototype(factory), {
66
"valueOf" => factory.pure_fn_returns_boolean,
77
})

crates/tree_shaker/src/builtins/prototypes/function.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{
33
entity::{Entity, EntityFactory},
44
init_prototype,
55
};
66
use oxc::semantic::SymbolId;
77
use oxc_index::Idx;
88

9-
pub fn create_function_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
9+
pub fn create_function_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
1010
init_prototype!("Function", create_object_prototype(factory), {
1111
"apply" => factory.implemented_builtin_fn("Function::apply", |analyzer, dep, this, args| {
1212
let mut args = args.destruct_as_array(analyzer, dep, 2, false).0;

crates/tree_shaker/src/builtins/prototypes/mod.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ use rustc_hash::FxHashMap;
2424
use super::Builtins;
2525

2626
#[derive(Default)]
27-
pub struct Prototype<'a> {
27+
pub struct BuiltinPrototype<'a> {
2828
name: &'static str,
2929
string_keyed: FxHashMap<&'static str, Entity<'a>>,
3030
symbol_keyed: FxHashMap<SymbolId, Entity<'a>>,
3131
}
3232

33-
impl<'a> fmt::Debug for Prototype<'a> {
33+
impl<'a> fmt::Debug for BuiltinPrototype<'a> {
3434
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3535
f.write_str(format!("Prototype({})", self.name).as_str())
3636
}
3737
}
3838

39-
impl<'a> Prototype<'a> {
39+
impl<'a> BuiltinPrototype<'a> {
4040
pub fn with_name(mut self, name: &'static str) -> Self {
4141
self.name = name;
4242
self
@@ -91,17 +91,17 @@ impl<'a> Prototype<'a> {
9191
}
9292

9393
pub struct BuiltinPrototypes<'a> {
94-
pub array: Prototype<'a>,
95-
pub bigint: Prototype<'a>,
96-
pub boolean: Prototype<'a>,
97-
pub function: Prototype<'a>,
98-
pub null: Prototype<'a>,
99-
pub number: Prototype<'a>,
100-
pub object: Prototype<'a>,
101-
pub promise: Prototype<'a>,
102-
pub regexp: Prototype<'a>,
103-
pub string: Prototype<'a>,
104-
pub symbol: Prototype<'a>,
94+
pub array: BuiltinPrototype<'a>,
95+
pub bigint: BuiltinPrototype<'a>,
96+
pub boolean: BuiltinPrototype<'a>,
97+
pub function: BuiltinPrototype<'a>,
98+
pub null: BuiltinPrototype<'a>,
99+
pub number: BuiltinPrototype<'a>,
100+
pub object: BuiltinPrototype<'a>,
101+
pub promise: BuiltinPrototype<'a>,
102+
pub regexp: BuiltinPrototype<'a>,
103+
pub string: BuiltinPrototype<'a>,
104+
pub symbol: BuiltinPrototype<'a>,
105105
}
106106

107107
impl<'a> Builtins<'a> {
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use super::Prototype;
1+
use super::BuiltinPrototype;
22
use crate::entity::EntityFactory;
33

4-
pub fn create_null_prototype<'a>(_factory: &EntityFactory<'a>) -> Prototype<'a> {
5-
Prototype::default().with_name("null")
4+
pub fn create_null_prototype<'a>(_factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
5+
BuiltinPrototype::default().with_name("null")
66
}

crates/tree_shaker/src/builtins/prototypes/number.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_number_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_number_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("Number", create_object_prototype(factory), {
66
"toExponential" => factory.pure_fn_returns_string,
77
"toFixed" => factory.pure_fn_returns_string,

crates/tree_shaker/src/builtins/prototypes/object.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{null::create_null_prototype, Prototype};
1+
use super::{null::create_null_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_object_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_object_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("Object", create_null_prototype(factory), {
66
"constructor" => factory.immutable_unknown,
77
"hasOwnProperty" => factory.pure_fn_returns_boolean,

crates/tree_shaker/src/builtins/prototypes/promise.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_promise_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_promise_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("Promise", create_object_prototype(factory), {
66
"finally" => factory.immutable_unknown,
77
"then" => factory.immutable_unknown,

crates/tree_shaker/src/builtins/prototypes/regexp.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_regexp_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_regexp_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("RegExp", create_object_prototype(factory), {
66
"exec" => factory.pure_fn_returns_unknown,
77
"test" => factory.pure_fn_returns_boolean,

crates/tree_shaker/src/builtins/prototypes/string.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_string_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_string_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("String", create_object_prototype(factory), {
66
"anchor" => factory.pure_fn_returns_string,
77
"at" => factory.pure_fn_returns_unknown,

crates/tree_shaker/src/builtins/prototypes/symbol.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{object::create_object_prototype, Prototype};
1+
use super::{object::create_object_prototype, BuiltinPrototype};
22
use crate::{entity::EntityFactory, init_prototype};
33

4-
pub fn create_symbol_prototype<'a>(factory: &EntityFactory<'a>) -> Prototype<'a> {
4+
pub fn create_symbol_prototype<'a>(factory: &EntityFactory<'a>) -> BuiltinPrototype<'a> {
55
init_prototype!("Symbol", create_object_prototype(factory), {
66
"toString" => factory.pure_fn_returns_string,
77
"valueOf" => factory.pure_fn_returns_symbol,

crates/tree_shaker/src/consumable/collector.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ impl<'a, T: ConsumableTrait<'a> + 'a> ConsumableCollector<'a, T> {
4646
self.try_collect(factory).unwrap_or(factory.empty_consumable)
4747
}
4848

49-
pub fn consume_all(self, analyzer: &mut Analyzer<'a>) {
50-
for value in self.current {
49+
pub fn consume_all(&self, analyzer: &mut Analyzer<'a>) {
50+
for value in &self.current {
5151
value.consume(analyzer);
5252
}
5353

crates/tree_shaker/src/consumable/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub struct Consumable<'a>(pub &'a (dyn ConsumableTrait<'a> + 'a));
1919
pub type ConsumableVec<'a> = Vec<Consumable<'a>>;
2020

2121
impl<'a> Analyzer<'a> {
22+
#[inline]
2223
pub fn consume(&mut self, dep: impl ConsumableTrait<'a> + 'a) {
2324
dep.consume(self);
2425
}

crates/tree_shaker/src/entity/arguments.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::{
2-
consumed_object, Entity, EntityTrait, EnumeratedProperties, IteratedElements, TypeofResult,
2+
consumed_object, Entity, EntityTrait, EnumeratedProperties, IteratedElements, ObjectPrototype,
3+
TypeofResult,
34
};
45
use crate::{analyzer::Analyzer, consumable::Consumable, use_consumed_flag};
56
use std::cell::Cell;
@@ -141,6 +142,14 @@ impl<'a> EntityTrait<'a> for ArgumentsEntity<'a> {
141142
unreachable!()
142143
}
143144

145+
fn get_constructor_prototype(
146+
&'a self,
147+
_analyzer: &Analyzer<'a>,
148+
_dep: Consumable<'a>,
149+
) -> Option<(Consumable<'a>, ObjectPrototype<'a>, ObjectPrototype<'a>)> {
150+
unreachable!()
151+
}
152+
144153
fn test_typeof(&self) -> TypeofResult {
145154
unreachable!()
146155
}

crates/tree_shaker/src/entity/builtin_fn.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{
22
consumed_object, never::NeverEntity, Entity, EntityFactory, EntityTrait, EnumeratedProperties,
3-
IteratedElements, ObjectEntity, TypeofResult,
3+
IteratedElements, ObjectEntity, ObjectPrototype, TypeofResult,
44
};
55
use crate::{analyzer::Analyzer, consumable::Consumable};
66
use std::fmt::Debug;
@@ -150,6 +150,14 @@ impl<'a, T: BuiltinFnEntity<'a>> EntityTrait<'a> for T {
150150
analyzer.factory.string("")
151151
}
152152

153+
fn get_constructor_prototype(
154+
&'a self,
155+
_analyzer: &Analyzer<'a>,
156+
_dep: Consumable<'a>,
157+
) -> Option<(Consumable<'a>, ObjectPrototype<'a>, ObjectPrototype<'a>)> {
158+
todo!()
159+
}
160+
153161
fn test_typeof(&self) -> TypeofResult {
154162
TypeofResult::Function
155163
}
@@ -217,7 +225,7 @@ impl<'a> Analyzer<'a> {
217225
#[cfg(feature = "flame")]
218226
name: _name,
219227
implementation,
220-
object: Some(self.new_function_object()),
228+
object: Some(self.new_function_object(None).0),
221229
})
222230
}
223231
}

crates/tree_shaker/src/entity/class.rs

+3-7
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ use super::{
22
consumed_object, Entity, EntityTrait, EnumeratedProperties, IteratedElements, ObjectEntity,
33
TypeofResult,
44
};
5-
use crate::{
6-
analyzer::Analyzer, consumable::Consumable, module::ModuleId, scope::VariableScopeId,
7-
use_consumed_flag,
8-
};
5+
use crate::{analyzer::Analyzer, consumable::Consumable, module::ModuleId, use_consumed_flag};
96
use oxc::ast::ast::Class;
10-
use std::{cell::Cell, rc::Rc};
7+
use std::cell::Cell;
118

129
#[derive(Debug)]
1310
pub struct ClassEntity<'a> {
@@ -16,8 +13,7 @@ pub struct ClassEntity<'a> {
1613
pub node: &'a Class<'a>,
1714
pub keys: Vec<Option<Entity<'a>>>,
1815
pub statics: &'a ObjectEntity<'a>,
19-
pub super_class: Option<Entity<'a>>,
20-
pub variable_scope_stack: Rc<Vec<VariableScopeId>>,
16+
pub prototype: &'a ObjectEntity<'a>,
2117
}
2218

2319
impl<'a> EntityTrait<'a> for ClassEntity<'a> {

crates/tree_shaker/src/entity/computed.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::{
2-
Entity, EntityTrait, EnumeratedProperties, IteratedElements, LiteralEntity, TypeofResult,
2+
Entity, EntityTrait, EnumeratedProperties, IteratedElements, LiteralEntity, ObjectPrototype,
3+
TypeofResult,
34
};
45
use crate::{
56
analyzer::Analyzer,
@@ -137,6 +138,16 @@ impl<'a, T: ConsumableTrait<'a> + Copy + 'a> EntityTrait<'a> for ComputedEntity<
137138
self.val.get_own_keys(analyzer)
138139
}
139140

141+
fn get_constructor_prototype(
142+
&'a self,
143+
analyzer: &Analyzer<'a>,
144+
dep: Consumable<'a>,
145+
) -> Option<(Consumable<'a>, ObjectPrototype<'a>, ObjectPrototype<'a>)> {
146+
let (dep, statics, prototype) = self.val.get_constructor_prototype(analyzer, dep)?;
147+
let dep = self.forward_dep(dep, analyzer);
148+
Some((dep, statics, prototype))
149+
}
150+
140151
fn test_typeof(&self) -> TypeofResult {
141152
self.val.test_typeof()
142153
}

0 commit comments

Comments
 (0)