From 87978499a3c7b950cc13396f7440aae1fa18bcc7 Mon Sep 17 00:00:00 2001 From: IWANABETHATGUY Date: Sun, 24 Nov 2024 23:24:32 +0800 Subject: [PATCH] feat(oxc_transformer): replace_global_define destructuring assignment optimization (#7449) 1. Closed #7382 Co-authored-by: Hiroshi Ogawa --- .../src/plugins/replace_global_defines.rs | 65 ++++++++++++++++++- .../plugins/replace_global_defines.rs | 33 ++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/crates/oxc_transformer/src/plugins/replace_global_defines.rs b/crates/oxc_transformer/src/plugins/replace_global_defines.rs index 72f08f963..fbd991fae 100644 --- a/crates/oxc_transformer/src/plugins/replace_global_defines.rs +++ b/crates/oxc_transformer/src/plugins/replace_global_defines.rs @@ -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]. /// @@ -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 @@ -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 +} diff --git a/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs b/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs index 84ac9bbf8..2c25882d6 100644 --- a/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs +++ b/crates/oxc_transformer/tests/integrations/plugins/replace_global_defines.rs @@ -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(), + ); +}