Summary
I did a static performance audit of crates/swc_ecma_transformer/src and found several hotspots that can become expensive on large files/classes.
Most of the risk comes from:
Vec::insert(0, ...) / Vec::insert(i, ...) patterns inside loops (potential O(n^2) shifts)
Repeated remove/insert on Vec<ModuleItem> while iterating
Repeated full subtree scans in hot hooks
Linear membership checks on Vec where HashSet would be O(1)
Findings
1) Repeated front insertion in object-rest lowering (O(n^2) shifting)
In multiple hooks, we prepend statements by repeatedly calling insert(0, stmt):
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:279
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:309
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:357
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:386
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:410
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:444
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:576
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:614
If many statements are prepended to large bodies, this repeatedly shifts the tail.
Suggestion: build a new vector with capacity and append [prepended..., original...], or use a small deque-style buffering strategy before one final materialization.
2) remove(i) + multiple insert(...) in exit_module_items (O(n^2) risk)
object_rest_spread::exit_module_items mutates items in-place with middle removes/inserts while iterating:
remove(i): crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:478
multiple insert(...): crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:493, :509, :513
whole loop: crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:460-537
On modules with many exports, this can degenerate into repeated shifts.
Suggestion: transform via a new output vector (Vec::with_capacity) in a single pass.
3) Statement injector currently performs repeated Vec::insert in reverse pass
common/statement_injector.rs collects insertion plans, then applies them using many insert calls:
module items insertion loop: crates/swc_ecma_transformer/src/common/statement_injector.rs:129-155
statement insertion loop: crates/swc_ecma_transformer/src/common/statement_injector.rs:171-197
This is also shift-heavy and can become O(n^2) with many injected statements.
Suggestion: rebuild the vector once, emitting before + original + after per element.
4) Private-property pass stores membership sets as Vec and uses repeated .contains
ClassData tracks methods and statics as Vec<Atom>:
definitions: crates/swc_ecma_transformer/src/es2022/private_property_in_object.rs:132, :135
membership checks in hot path: :144, :149, :371, :372
These checks are linear in the number of private members.
Suggestion: switch methods and statics to FxHashSet<Atom> (or maintain both ordered vec + set only if ordering is needed).
5) Full subtree scan in enter_assign_pat for every assign pattern
private_property_in_object performs an explicit visitor walk on each AssignPat.right:
crates/swc_ecma_transformer/src/es2022/private_property_in_object.rs:282-290
This adds extra traversals in addition to the main transform traversal.
Suggestion: avoid per-node ad-hoc scans by carrying a lightweight context flag from the main traversal or restricting this scan to cases that already matched a fast precheck.
6) Function-name transform does full usage scan per candidate function/class
function_name calls IdentUsageFinder::find for each anonymous fn/class assignment candidate:
crates/swc_ecma_transformer/src/es2015/function_name.rs:111
crates/swc_ecma_transformer/src/es2015/function_name.rs:126
On code with many such candidates and large bodies, this introduces repeated full-body scans.
Suggestion: add cheap fast-path guards before full scan, or cache/short-circuit when the candidate name cannot appear.
7) Async-to-generator can do a second full recursive walk to replace this
In constructor-arrow cases using this, we run an additional recursive replacement pass over the arrow body:
call site: crates/swc_ecma_transformer/src/es2017/async_to_generator.rs:189-195
recursive walkers: :876-1331
This is semantically valid but can be expensive for large nested arrow bodies.
Suggestion: integrate replacement into the main traversal path when possible, or narrow second-pass scope with stronger prechecks.
Suggested follow-up
Add micro-bench fixtures for:
many function params/object-rest cases
many export declarations requiring rewrite
large class with many private members and #x in y checks
Measure before/after with wall time + allocation counts.
Prioritize findings ecmascript parser #1 , Clean up #2 , EcmaScript lexer is inefficient #3 , Transformers for ecmascript #4 first (highest expected impact).
Scope note
This report is static-analysis-only (no runtime profiling yet).
Summary
I did a static performance audit of
crates/swc_ecma_transformer/srcand found several hotspots that can become expensive on large files/classes.Most of the risk comes from:
Vec::insert(0, ...)/Vec::insert(i, ...)patterns inside loops (potentialO(n^2)shifts)remove/insertonVec<ModuleItem>while iteratingVecwhereHashSetwould beO(1)Findings
1) Repeated front insertion in object-rest lowering (
O(n^2)shifting)In multiple hooks, we prepend statements by repeatedly calling
insert(0, stmt):crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:279crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:309crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:357crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:386crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:410crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:444crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:576crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:614If many statements are prepended to large bodies, this repeatedly shifts the tail.
Suggestion: build a new vector with capacity and append
[prepended..., original...], or use a small deque-style buffering strategy before one final materialization.2)
remove(i)+ multipleinsert(...)inexit_module_items(O(n^2)risk)object_rest_spread::exit_module_itemsmutatesitemsin-place with middle removes/inserts while iterating:remove(i):crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:478insert(...):crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:493,:509,:513crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:460-537On modules with many exports, this can degenerate into repeated shifts.
Suggestion: transform via a new output vector (
Vec::with_capacity) in a single pass.3) Statement injector currently performs repeated
Vec::insertin reverse passcommon/statement_injector.rscollects insertion plans, then applies them using manyinsertcalls:crates/swc_ecma_transformer/src/common/statement_injector.rs:129-155crates/swc_ecma_transformer/src/common/statement_injector.rs:171-197This is also shift-heavy and can become
O(n^2)with many injected statements.Suggestion: rebuild the vector once, emitting
before + original + afterper element.4) Private-property pass stores membership sets as
Vecand uses repeated.containsClassDatatracksmethodsandstaticsasVec<Atom>:crates/swc_ecma_transformer/src/es2022/private_property_in_object.rs:132,:135:144,:149,:371,:372These checks are linear in the number of private members.
Suggestion: switch
methodsandstaticstoFxHashSet<Atom>(or maintain both ordered vec + set only if ordering is needed).5) Full subtree scan in
enter_assign_patfor every assign patternprivate_property_in_objectperforms an explicit visitor walk on eachAssignPat.right:crates/swc_ecma_transformer/src/es2022/private_property_in_object.rs:282-290This adds extra traversals in addition to the main transform traversal.
Suggestion: avoid per-node ad-hoc scans by carrying a lightweight context flag from the main traversal or restricting this scan to cases that already matched a fast precheck.
6) Function-name transform does full usage scan per candidate function/class
function_namecallsIdentUsageFinder::findfor each anonymous fn/class assignment candidate:crates/swc_ecma_transformer/src/es2015/function_name.rs:111crates/swc_ecma_transformer/src/es2015/function_name.rs:126On code with many such candidates and large bodies, this introduces repeated full-body scans.
Suggestion: add cheap fast-path guards before full scan, or cache/short-circuit when the candidate name cannot appear.
7) Async-to-generator can do a second full recursive walk to replace
thisIn constructor-arrow cases using
this, we run an additional recursive replacement pass over the arrow body:crates/swc_ecma_transformer/src/es2017/async_to_generator.rs:189-195:876-1331This is semantically valid but can be expensive for large nested arrow bodies.
Suggestion: integrate replacement into the main traversal path when possible, or narrow second-pass scope with stronger prechecks.
Suggested follow-up
#x in ychecksScope note
This report is static-analysis-only (no runtime profiling yet).