mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
refactor(minifier): replace VisitMut with Traverse for inject and define plugins (#5705)
closes #5704
This commit is contained in:
parent
945d2744ae
commit
21e2df57a0
4 changed files with 68 additions and 56 deletions
|
|
@ -1,9 +1,12 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cow_utils::CowUtils;
|
use cow_utils::CowUtils;
|
||||||
|
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut};
|
use oxc_ast::{ast::*, AstBuilder};
|
||||||
use oxc_semantic::{ScopeTree, SymbolTable};
|
use oxc_semantic::{ScopeTree, SymbolTable};
|
||||||
use oxc_span::{CompactStr, SPAN};
|
use oxc_span::{CompactStr, SPAN};
|
||||||
use std::sync::Arc;
|
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
|
||||||
|
|
||||||
use super::replace_global_defines::{DotDefine, ReplaceGlobalDefines};
|
use super::replace_global_defines::{DotDefine, ReplaceGlobalDefines};
|
||||||
|
|
||||||
|
|
@ -100,12 +103,18 @@ impl<'a> From<&InjectImport> for DotDefineState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct InjectGlobalVariablesReturn {
|
||||||
|
pub symbols: SymbolTable,
|
||||||
|
pub scopes: ScopeTree,
|
||||||
|
}
|
||||||
|
|
||||||
/// Injects import statements for global variables.
|
/// Injects import statements for global variables.
|
||||||
///
|
///
|
||||||
/// References:
|
/// References:
|
||||||
///
|
///
|
||||||
/// * <https://www.npmjs.com/package/@rollup/plugin-inject>
|
/// * <https://www.npmjs.com/package/@rollup/plugin-inject>
|
||||||
pub struct InjectGlobalVariables<'a, 'b> {
|
pub struct InjectGlobalVariables<'a> {
|
||||||
ast: AstBuilder<'a>,
|
ast: AstBuilder<'a>,
|
||||||
config: InjectGlobalVariablesConfig,
|
config: InjectGlobalVariablesConfig,
|
||||||
|
|
||||||
|
|
@ -116,36 +125,32 @@ pub struct InjectGlobalVariables<'a, 'b> {
|
||||||
/// Identifiers for which dot define replaced a member expression.
|
/// Identifiers for which dot define replaced a member expression.
|
||||||
replaced_dot_defines:
|
replaced_dot_defines:
|
||||||
Vec<(/* identifier of member expression */ CompactStr, /* local */ CompactStr)>,
|
Vec<(/* identifier of member expression */ CompactStr, /* local */ CompactStr)>,
|
||||||
|
|
||||||
symbols: &'b mut SymbolTable, // will be used to keep symbols in sync
|
|
||||||
scopes: &'b mut ScopeTree,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> VisitMut<'a> for InjectGlobalVariables<'a, 'b> {
|
impl<'a> Traverse<'a> for InjectGlobalVariables<'a> {
|
||||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.replace_dot_defines(expr);
|
self.replace_dot_defines(expr, ctx);
|
||||||
walk_mut::walk_expression(self, expr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
|
impl<'a> InjectGlobalVariables<'a> {
|
||||||
pub fn new(
|
pub fn new(allocator: &'a Allocator, config: InjectGlobalVariablesConfig) -> Self {
|
||||||
allocator: &'a Allocator,
|
|
||||||
symbols: &'b mut SymbolTable,
|
|
||||||
scopes: &'b mut ScopeTree,
|
|
||||||
config: InjectGlobalVariablesConfig,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
ast: AstBuilder::new(allocator),
|
ast: AstBuilder::new(allocator),
|
||||||
config,
|
config,
|
||||||
dot_defines: vec![],
|
dot_defines: vec![],
|
||||||
replaced_dot_defines: vec![],
|
replaced_dot_defines: vec![],
|
||||||
symbols,
|
|
||||||
scopes,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&mut self, program: &mut Program<'a>) {
|
pub fn build(
|
||||||
|
&mut self,
|
||||||
|
symbols: SymbolTable,
|
||||||
|
scopes: ScopeTree,
|
||||||
|
program: &mut Program<'a>,
|
||||||
|
) -> InjectGlobalVariablesReturn {
|
||||||
|
let mut symbols = symbols;
|
||||||
|
let mut scopes = scopes;
|
||||||
// Step 1: slow path where visiting the AST is required to replace dot defines.
|
// Step 1: slow path where visiting the AST is required to replace dot defines.
|
||||||
let dot_defines = self
|
let dot_defines = self
|
||||||
.config
|
.config
|
||||||
|
|
@ -157,7 +162,7 @@ impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
|
||||||
|
|
||||||
if !dot_defines.is_empty() {
|
if !dot_defines.is_empty() {
|
||||||
self.dot_defines = dot_defines;
|
self.dot_defines = dot_defines;
|
||||||
self.visit_program(program);
|
(symbols, scopes) = traverse_mut(self, self.ast.allocator, program, symbols, scopes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: find all the injects that are referenced.
|
// Step 2: find all the injects that are referenced.
|
||||||
|
|
@ -172,17 +177,19 @@ impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
|
||||||
} else if self.replaced_dot_defines.iter().any(|d| d.0 == i.specifier.local()) {
|
} else if self.replaced_dot_defines.iter().any(|d| d.0 == i.specifier.local()) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
self.scopes.root_unresolved_references().contains_key(i.specifier.local())
|
scopes.root_unresolved_references().contains_key(i.specifier.local())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if injects.is_empty() {
|
if injects.is_empty() {
|
||||||
return;
|
return InjectGlobalVariablesReturn { symbols, scopes };
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inject_imports(&injects, program);
|
self.inject_imports(&injects, program);
|
||||||
|
|
||||||
|
InjectGlobalVariablesReturn { symbols, scopes }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inject_imports(&self, injects: &[InjectImport], program: &mut Program<'a>) {
|
fn inject_imports(&self, injects: &[InjectImport], program: &mut Program<'a>) {
|
||||||
|
|
@ -227,10 +234,10 @@ impl<'a, 'b> InjectGlobalVariables<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>) {
|
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
if let Expression::StaticMemberExpression(member) = expr {
|
if let Expression::StaticMemberExpression(member) = expr {
|
||||||
for DotDefineState { dot_define, value_atom } in &mut self.dot_defines {
|
for DotDefineState { dot_define, value_atom } in &mut self.dot_defines {
|
||||||
if ReplaceGlobalDefines::is_dot_define(self.symbols, dot_define, member) {
|
if ReplaceGlobalDefines::is_dot_define(ctx.symbols(), dot_define, member) {
|
||||||
// If this is first replacement made for this dot define,
|
// If this is first replacement made for this dot define,
|
||||||
// create `Atom` for replacement, and record in `replaced_dot_defines`
|
// create `Atom` for replacement, and record in `replaced_dot_defines`
|
||||||
if value_atom.is_none() {
|
if value_atom.is_none() {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use std::{cmp::Ordering, sync::Arc};
|
use std::{cmp::Ordering, sync::Arc};
|
||||||
|
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_ast::{ast::*, visit::walk_mut, AstBuilder, VisitMut};
|
use oxc_ast::ast::*;
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
use oxc_semantic::{IsGlobalReference, SymbolTable};
|
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
||||||
use oxc_span::{CompactStr, SourceType};
|
use oxc_span::{CompactStr, SourceType};
|
||||||
use oxc_syntax::identifier::is_identifier_name;
|
use oxc_syntax::identifier::is_identifier_name;
|
||||||
|
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
|
||||||
|
|
||||||
/// Configuration for [ReplaceGlobalDefines].
|
/// Configuration for [ReplaceGlobalDefines].
|
||||||
///
|
///
|
||||||
|
|
@ -162,6 +163,12 @@ impl ReplaceGlobalDefinesConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct ReplaceGlobalDefinesReturn {
|
||||||
|
pub symbols: SymbolTable,
|
||||||
|
pub scopes: ScopeTree,
|
||||||
|
}
|
||||||
|
|
||||||
/// Replace Global Defines.
|
/// Replace Global Defines.
|
||||||
///
|
///
|
||||||
/// References:
|
/// References:
|
||||||
|
|
@ -169,46 +176,44 @@ impl ReplaceGlobalDefinesConfig {
|
||||||
/// * <https://esbuild.github.io/api/#define>
|
/// * <https://esbuild.github.io/api/#define>
|
||||||
/// * <https://github.com/terser/terser?tab=readme-ov-file#conditional-compilation>
|
/// * <https://github.com/terser/terser?tab=readme-ov-file#conditional-compilation>
|
||||||
/// * <https://github.com/evanw/esbuild/blob/9c13ae1f06dfa909eb4a53882e3b7e4216a503fe/internal/config/globals.go#L852-L1014>
|
/// * <https://github.com/evanw/esbuild/blob/9c13ae1f06dfa909eb4a53882e3b7e4216a503fe/internal/config/globals.go#L852-L1014>
|
||||||
pub struct ReplaceGlobalDefines<'a, 'b> {
|
pub struct ReplaceGlobalDefines<'a> {
|
||||||
ast: AstBuilder<'a>,
|
|
||||||
symbols: &'b mut SymbolTable,
|
|
||||||
config: ReplaceGlobalDefinesConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> VisitMut<'a> for ReplaceGlobalDefines<'a, 'b> {
|
|
||||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
|
||||||
self.replace_identifier_defines(expr);
|
|
||||||
self.replace_dot_defines(expr);
|
|
||||||
walk_mut::walk_expression(self, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> ReplaceGlobalDefines<'a, 'b> {
|
|
||||||
pub fn new(
|
|
||||||
allocator: &'a Allocator,
|
allocator: &'a Allocator,
|
||||||
symbols: &'b mut SymbolTable,
|
|
||||||
config: ReplaceGlobalDefinesConfig,
|
config: ReplaceGlobalDefinesConfig,
|
||||||
) -> Self {
|
}
|
||||||
Self { ast: AstBuilder::new(allocator), symbols, config }
|
|
||||||
|
impl<'a> Traverse<'a> for ReplaceGlobalDefines<'a> {
|
||||||
|
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
self.replace_identifier_defines(expr, ctx);
|
||||||
|
self.replace_dot_defines(expr, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ReplaceGlobalDefines<'a> {
|
||||||
|
pub fn new(allocator: &'a Allocator, config: ReplaceGlobalDefinesConfig) -> Self {
|
||||||
|
Self { allocator, config }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&mut self, program: &mut Program<'a>) {
|
pub fn build(
|
||||||
self.visit_program(program);
|
&mut self,
|
||||||
|
symbols: SymbolTable,
|
||||||
|
scopes: ScopeTree,
|
||||||
|
program: &mut Program<'a>,
|
||||||
|
) -> ReplaceGlobalDefinesReturn {
|
||||||
|
let (symbols, scopes) = traverse_mut(self, self.allocator, program, symbols, scopes);
|
||||||
|
ReplaceGlobalDefinesReturn { symbols, scopes }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a new expression because we don't have ast clone right now.
|
// Construct a new expression because we don't have ast clone right now.
|
||||||
fn parse_value(&self, source_text: &str) -> Expression<'a> {
|
fn parse_value(&self, source_text: &str) -> Expression<'a> {
|
||||||
// Allocate the string lazily because replacement happens rarely.
|
// Allocate the string lazily because replacement happens rarely.
|
||||||
let source_text = self.ast.allocator.alloc(source_text.to_string());
|
let source_text = self.allocator.alloc(source_text.to_string());
|
||||||
// Unwrapping here, it should already be checked by [ReplaceGlobalDefinesConfig::new].
|
// Unwrapping here, it should already be checked by [ReplaceGlobalDefinesConfig::new].
|
||||||
Parser::new(self.ast.allocator, source_text, SourceType::default())
|
Parser::new(self.allocator, source_text, SourceType::default()).parse_expression().unwrap()
|
||||||
.parse_expression()
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_identifier_defines(&self, expr: &mut Expression<'a>) {
|
fn replace_identifier_defines(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
let Expression::Identifier(ident) = expr else { return };
|
let Expression::Identifier(ident) = expr else { return };
|
||||||
if !ident.is_global_reference(self.symbols) {
|
if !ident.is_global_reference(ctx.symbols()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (key, value) in &self.config.0.identifier {
|
for (key, value) in &self.config.0.identifier {
|
||||||
|
|
@ -220,12 +225,12 @@ impl<'a, 'b> ReplaceGlobalDefines<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>) {
|
fn replace_dot_defines(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
let Expression::StaticMemberExpression(member) = expr else {
|
let Expression::StaticMemberExpression(member) = expr else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
for dot_define in &self.config.0.dot {
|
for dot_define in &self.config.0.dot {
|
||||||
if Self::is_dot_define(self.symbols, dot_define, member) {
|
if Self::is_dot_define(ctx.symbols(), dot_define, member) {
|
||||||
let value = self.parse_value(&dot_define.value);
|
let value = self.parse_value(&dot_define.value);
|
||||||
*expr = value;
|
*expr = value;
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ pub(crate) fn test(source_text: &str, expected: &str, config: InjectGlobalVariab
|
||||||
let allocator = Allocator::default();
|
let allocator = Allocator::default();
|
||||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||||
let program = allocator.alloc(ret.program);
|
let program = allocator.alloc(ret.program);
|
||||||
let (mut symbols, mut scopes) = SemanticBuilder::new(source_text)
|
let (symbols, scopes) = SemanticBuilder::new(source_text)
|
||||||
.build(program)
|
.build(program)
|
||||||
.semantic
|
.semantic
|
||||||
.into_symbol_table_and_scope_tree();
|
.into_symbol_table_and_scope_tree();
|
||||||
InjectGlobalVariables::new(&allocator, &mut symbols, &mut scopes, config).build(program);
|
let _ = InjectGlobalVariables::new(&allocator, config).build(symbols, scopes, program);
|
||||||
let result = CodeGenerator::new()
|
let result = CodeGenerator::new()
|
||||||
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
||||||
.build(program)
|
.build(program)
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ pub(crate) fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefin
|
||||||
let allocator = Allocator::default();
|
let allocator = Allocator::default();
|
||||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||||
let program = allocator.alloc(ret.program);
|
let program = allocator.alloc(ret.program);
|
||||||
let (mut symbols, _scopes) = SemanticBuilder::new(source_text)
|
let (symbols, scopes) = SemanticBuilder::new(source_text)
|
||||||
.build(program)
|
.build(program)
|
||||||
.semantic
|
.semantic
|
||||||
.into_symbol_table_and_scope_tree();
|
.into_symbol_table_and_scope_tree();
|
||||||
ReplaceGlobalDefines::new(&allocator, &mut symbols, config).build(program);
|
let _ = ReplaceGlobalDefines::new(&allocator, config).build(symbols, scopes, program);
|
||||||
let result = CodeGenerator::new()
|
let result = CodeGenerator::new()
|
||||||
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
||||||
.build(program)
|
.build(program)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue