Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oxc_transformer): replace_global_define destructuring assignment optimization #7449

Merged
merged 1 commit into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions crates/oxc_transformer/src/plugins/replace_global_defines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use oxc_parser::Parser;
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
use oxc_span::{CompactStr, SourceType};
use oxc_syntax::identifier::is_identifier_name;
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
use oxc_traverse::{traverse_mut, Ancestor, Traverse, TraverseCtx};
use rustc_hash::FxHashSet;

/// Configuration for [ReplaceGlobalDefines].
///
Expand Down Expand Up @@ -324,13 +325,13 @@ impl<'a> ReplaceGlobalDefines<'a> {
DotDefineMemberExpression::StaticMemberExpression(member),
) {
let value = self.parse_value(&dot_define.value);
return Some(value);
return Some(destructing_dot_define_optimizer(value, ctx));
}
}
for meta_property_define in &self.config.0.meta_property {
if Self::is_meta_property_define(meta_property_define, member) {
let value = self.parse_value(&meta_property_define.value);
return Some(value);
return Some(destructing_dot_define_optimizer(value, ctx));
}
}
None
Expand Down Expand Up @@ -498,3 +499,61 @@ fn static_property_name_of_computed_expr<'b, 'a: 'b>(
_ => None,
}
}

fn destructing_dot_define_optimizer<'ast>(
mut expr: Expression<'ast>,
ctx: &mut TraverseCtx<'ast>,
) -> Expression<'ast> {
let Expression::ObjectExpression(ref mut obj) = expr else { return expr };
let parent = ctx.parent();
let destruct_obj_pat = match parent {
Ancestor::VariableDeclaratorInit(declarator) => match declarator.id().kind {
BindingPatternKind::ObjectPattern(ref pat) => pat,
_ => return expr,
},
_ => {
return expr;
}
};
let mut needed_keys = FxHashSet::default();
for prop in &destruct_obj_pat.properties {
match prop.key.name() {
Some(key) => {
needed_keys.insert(key);
}
// if there exists a none static key, we can't optimize
None => {
return expr;
}
}
}

// here we iterate the object properties twice
// for the first time we check if all the keys are static
// for the second time we only keep the needed keys
// Another way to do this is mutate the objectExpr only the fly,
// but need to save the checkpoint(to return the original Expr if there are any dynamic key exists) which is a memory clone,
// cpu is faster than memory allocation
let mut should_preserved_keys = Vec::with_capacity(obj.properties.len());
for prop in &obj.properties {
let v = match prop {
ObjectPropertyKind::ObjectProperty(prop) => {
// not static key just preserve it
if let Some(name) = prop.key.name() {
needed_keys.contains(&name)
} else {
true
}
}
// not static key
ObjectPropertyKind::SpreadProperty(_) => true,
};
should_preserved_keys.push(v);
}

// we could ensure `should_preserved_keys` has the same length as `obj.properties`
// the method copy from std doc https://doc.rust-lang.org/std/vec/struct.Vec.html#examples-26
let mut iter = should_preserved_keys.iter();
obj.properties.retain(|_| *iter.next().unwrap());
expr
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,36 @@ fn optional_chain() {
test_same("a?.[b][c]", config.clone());
test_same("a[b]?.[c]", config.clone());
}

#[test]
fn dot_define_with_destruct() {
let config = ReplaceGlobalDefinesConfig::new(&[(
"process.env.NODE_ENV",
"{'a': 1, b: 2, c: true, d: {a: b}}",
)])
.unwrap();
test(
"const {a, c} = process.env.NODE_ENV",
"const { a, c } = {\n\t'a': 1,\n\tc: true};",
config.clone(),
);
// bailout
test(
"const {[any]: alias} = process.env.NODE_ENV",
"const { [any]: alias } = {\n\t'a': 1,\n\tb: 2,\n\tc: true,\n\td: { a: b }\n};",
config.clone(),
);

// should filterout unused key even rhs objectExpr has SpreadElement

let config = ReplaceGlobalDefinesConfig::new(&[(
"process.env.NODE_ENV",
"{'a': 1, b: 2, c: true, ...unknown}",
)])
.unwrap();
test(
"const {a} = process.env.NODE_ENV",
"const { a } = {\n\t'a': 1,\n\t...unknown\n};\n",
config.clone(),
);
}
Loading