diff --git a/crates/oxc_minifier/src/plugins/replace_global_defines.rs b/crates/oxc_minifier/src/plugins/replace_global_defines.rs index 86b02f13b0b3d7..74e0c0bc803363 100644 --- a/crates/oxc_minifier/src/plugins/replace_global_defines.rs +++ b/crates/oxc_minifier/src/plugins/replace_global_defines.rs @@ -21,6 +21,7 @@ pub struct ReplaceGlobalDefinesConfig(Arc); struct ReplaceGlobalDefinesConfigImpl { identifier_defines: Vec<(/* key */ CompactStr, /* value */ CompactStr)>, dot_defines: Vec, + meta_proeperty_defines: Vec, } #[derive(Debug)] @@ -31,6 +32,20 @@ pub struct DotDefine { pub postfix_wildcard: bool, } +#[derive(Debug)] +pub struct MetaPropertyDefine { + /// only store parts after `import.meta` + pub parts: Vec, + pub value: CompactStr, + pub postfix_wildcard: bool, +} + +impl MetaPropertyDefine { + pub fn new(parts: Vec, value: CompactStr, postfix_wildcard: bool) -> Self { + Self { parts, value, postfix_wildcard } + } +} + impl DotDefine { fn new(parts: Vec, value: CompactStr, postfix_wildcard: bool) -> Self { Self { parts, value, postfix_wildcard } @@ -39,7 +54,9 @@ impl DotDefine { enum IdentifierType { Identifier, - DotDefines { parts: Vec, postfix_wildcard: bool }, + DotDefines { parts: Vec }, + // import.meta.a + ImportMeta { parts: Vec, postfix_wildcard: bool }, } impl ReplaceGlobalDefinesConfig { @@ -51,6 +68,7 @@ impl ReplaceGlobalDefinesConfig { let allocator = Allocator::default(); let mut identifier_defines = vec![]; let mut dot_defines = vec![]; + let mut meta_proeperty_defines = vec![]; for (key, value) in defines { let key = key.as_ref(); @@ -61,8 +79,11 @@ impl ReplaceGlobalDefinesConfig { IdentifierType::Identifier => { identifier_defines.push((CompactStr::new(key), CompactStr::new(value))); } - IdentifierType::DotDefines { parts, postfix_wildcard } => { - dot_defines.push(DotDefine::new( + IdentifierType::DotDefines { parts } => { + dot_defines.push(DotDefine::new(parts, CompactStr::new(value), false)); + } + IdentifierType::ImportMeta { parts, postfix_wildcard } => { + meta_proeperty_defines.push(MetaPropertyDefine::new( parts, CompactStr::new(value), postfix_wildcard, @@ -70,10 +91,10 @@ impl ReplaceGlobalDefinesConfig { } } } - // Always move specific dot define before wildcard dot define + // Always move specific meta define before wildcard dot define // Keep other order unchanged // see test case replace_global_definitions_dot_with_postfix_mixed as an example - dot_defines.sort_by(|a, b| { + meta_proeperty_defines.sort_by(|a, b| { if !a.postfix_wildcard && b.postfix_wildcard { Ordering::Less } else if a.postfix_wildcard && b.postfix_wildcard { @@ -82,8 +103,11 @@ impl ReplaceGlobalDefinesConfig { Ordering::Equal } }); - - Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl { identifier_defines, dot_defines }))) + Ok(Self(Arc::new(ReplaceGlobalDefinesConfigImpl { + identifier_defines, + dot_defines, + meta_proeperty_defines, + }))) } fn check_key(key: &str) -> Result> { @@ -99,17 +123,38 @@ impl ReplaceGlobalDefinesConfig { } let normalized_parts_len = if parts[parts.len() - 1] == "*" { parts.len() - 1 } else { parts.len() }; + // We can ensure now the parts.len() >= 2 + let is_import_meta = parts[0] == "import" && parts[1] == "meta"; for part in &parts[0..normalized_parts_len] { if !is_identifier_name(part) { return Err(vec![OxcDiagnostic::error(format!("`{key}` is not an identifier."))]); } } - - Ok(IdentifierType::DotDefines { - parts: parts.iter().take(normalized_parts_len).map(|s| CompactStr::new(s)).collect(), - postfix_wildcard: parts.len() != normalized_parts_len, - }) + if is_import_meta { + Ok(IdentifierType::ImportMeta { + parts: parts + .iter() + .skip(2) + .take(normalized_parts_len - 2) + .map(|s| CompactStr::new(s)) + .collect(), + postfix_wildcard: normalized_parts_len != parts.len(), + }) + // StaticMemberExpression with postfix wildcard + } else if normalized_parts_len != parts.len() { + Err(vec![OxcDiagnostic::error(format!( + "postfix wildcard is only allowed for `import.meta`." + ))]) + } else { + Ok(IdentifierType::DotDefines { + parts: parts + .iter() + .take(normalized_parts_len) + .map(|s| CompactStr::new(s)) + .collect(), + }) + } } fn check_value(allocator: &Allocator, source_text: &str) -> Result<(), Vec> { @@ -170,27 +215,42 @@ impl<'a> ReplaceGlobalDefines<'a> { } fn replace_dot_defines(&mut self, expr: &mut Expression<'a>) { - if let Expression::StaticMemberExpression(member) = expr { - for dot_define in &self.config.0.dot_defines { - if Self::is_dot_define(dot_define, member) { - let value = self.parse_value(&dot_define.value); - *expr = value; - break; - } + let Expression::StaticMemberExpression(member) = expr else { + return; + }; + for dot_define in &self.config.0.dot_defines { + if Self::is_dot_define(dot_define, member) { + let value = self.parse_value(&dot_define.value); + *expr = value; + return; + } + } + // dbg!(&member); + for meta_proeperty_define in &self.config.0.meta_proeperty_defines { + let ret = Self::is_meta_property_define(meta_proeperty_define, member); + dbg!(&ret); + if ret { + let value = self.parse_value(&meta_proeperty_define.value); + *expr = value; + break; } } } - pub fn is_dot_define(dot_define: &DotDefine, member: &StaticMemberExpression<'a>) -> bool { - debug_assert!(dot_define.parts.len() > 1); - let mut current_part_member_expression = - Some(StaticMemberExpressionOrMeta::StaticMemberExpression(member)); + pub fn is_meta_property_define( + meta_define: &MetaPropertyDefine, + member: &StaticMemberExpression<'a>, + ) -> bool { + debug_assert!(meta_define.parts.len() >= 1); + + let mut current_part_member_expression = Some(member); let mut cur_part_name = &member.property.name; let mut has_matched_part = false; - let mut i = dot_define.parts.len() - 1; + let mut i = meta_define.parts.len() - 1; loop { - let part = &dot_define.parts[i]; + let part = &meta_define.parts[i]; let matched = cur_part_name.as_str() == part; + dbg!(&part, cur_part_name); if !matched { // Considering import.meta.env.* // ```js @@ -199,54 +259,77 @@ impl<'a> ReplaceGlobalDefines<'a> { // ``` // So we use has_matched_part to track if any part has matched. - if !dot_define.postfix_wildcard || has_matched_part { + dbg!(&meta_define.postfix_wildcard, has_matched_part); + if !meta_define.postfix_wildcard || has_matched_part { return false; } - } - - if i == 0 { - break; + } else { + has_matched_part = true; } current_part_member_expression = if let Some(member) = current_part_member_expression { - match member { - StaticMemberExpressionOrMeta::StaticMemberExpression(member) => { - match &member.object { - Expression::StaticMemberExpression(member) => { - cur_part_name = &member.property.name; - Some(StaticMemberExpressionOrMeta::StaticMemberExpression(member)) - } - Expression::MetaProperty(member) => { - cur_part_name = &member.property.name; - Some(StaticMemberExpressionOrMeta::MetaProperty(member)) - } - Expression::Identifier(ident) => { - cur_part_name = &ident.name; - None - } - _ => None, - } + match &member.object { + Expression::StaticMemberExpression(member) => { + cur_part_name = &member.property.name; + Some(member) } - StaticMemberExpressionOrMeta::MetaProperty(meta) => { - cur_part_name = &meta.meta.name; - None + Expression::MetaProperty(_) => { + return has_matched_part; } + Expression::Identifier(_) => { + return false; + } + _ => None, } } else { return false; }; + dbg!(&i, matched); + + if i == 0 && matched { + break; + } if matched { i -= 1; - has_matched_part = true; } } - has_matched_part + false } -} -enum StaticMemberExpressionOrMeta<'a> { - StaticMemberExpression(&'a StaticMemberExpression<'a>), - MetaProperty(&'a MetaProperty<'a>), + pub fn is_dot_define(dot_define: &DotDefine, member: &StaticMemberExpression<'a>) -> bool { + debug_assert!(dot_define.parts.len() > 1); + + let mut current_part_member_expression = Some(member); + let mut cur_part_name = &member.property.name; + + for (i, part) in dot_define.parts.iter().enumerate().rev() { + if cur_part_name.as_str() != part { + return false; + } + + if i == 0 { + break; + } + + current_part_member_expression = if let Some(member) = current_part_member_expression { + match &member.object { + Expression::StaticMemberExpression(member) => { + cur_part_name = &member.property.name; + Some(member) + } + Expression::Identifier(ident) => { + cur_part_name = &ident.name; + None + } + _ => None, + } + } else { + return false; + }; + } + + true + } } diff --git a/crates/oxc_minifier/tests/plugins/replace_global_defines.rs b/crates/oxc_minifier/tests/plugins/replace_global_defines.rs index 1857ccdd70cf68..902e3e6b0e9c53 100644 --- a/crates/oxc_minifier/tests/plugins/replace_global_defines.rs +++ b/crates/oxc_minifier/tests/plugins/replace_global_defines.rs @@ -46,12 +46,8 @@ fn replace_global_definitions_dot() { #[test] fn replace_global_definitions_dot_with_postfix_wildcard() { { - let config = ReplaceGlobalDefinesConfig::new(&[ - ("process.env.NODE_ENV.*", "production"), - ("import.meta.env.*", "undefined"), - ]) - .unwrap(); - test("process.env.NODE_ENV.test", "production", config.clone()); + let config = + ReplaceGlobalDefinesConfig::new(&[("import.meta.env.*", "undefined")]).unwrap(); test("import.meta.env.result", "undefined", config.clone()); test("import.meta.env", "undefined", config); } @@ -61,12 +57,10 @@ fn replace_global_definitions_dot_with_postfix_wildcard() { fn replace_global_definitions_dot_with_postfix_mixed() { { let config = ReplaceGlobalDefinesConfig::new(&[ - ("process.env.NODE_ENV.*", "production"), ("import.meta.env.*", "undefined"), ("import.meta.env", "env"), ]) .unwrap(); - test("process.env.NODE_ENV.test", "production", config.clone()); test("import.meta.env.result", "undefined", config.clone()); test("import.meta.env", "env", config); }