Skip to content

Commit

Permalink
feat(minifier): add ReplaceGlobalDefinitions ast pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Jun 21, 2024
1 parent 5d2faba commit 3155895
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/oxc_minifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ oxc_ast = { workspace = true }
oxc_semantic = { workspace = true }
oxc_syntax = { workspace = true }
oxc_index = { workspace = true }
oxc_parser = { workspace = true }
oxc_diagnostics = { workspace = true }

num-bigint = { workspace = true }
itertools = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_minifier/src/ast_passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

mod remove_dead_code;
mod remove_parens;
mod replace_global_defines;

pub use remove_dead_code::RemoveDeadCode;
pub use remove_parens::RemoveParens;
pub use replace_global_defines::ReplaceGlobalDefines;
1 change: 0 additions & 1 deletion crates/oxc_minifier/src/ast_passes/remove_dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use oxc_span::SPAN;
/// Remove Dead Code from the AST.
///
/// Terser option: `dead_code: true`.
#[derive(Clone, Copy)]
pub struct RemoveDeadCode<'a> {
ast: AstBuilder<'a>,
}
Expand Down
3 changes: 1 addition & 2 deletions crates/oxc_minifier/src/ast_passes/remove_parens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use oxc_ast::{
};

/// Remove Parenthesized Expression from the AST.
#[derive(Clone, Copy)]
pub struct RemoveParens<'a> {
ast: AstBuilder<'a>,
}
Expand All @@ -20,7 +19,7 @@ impl<'a> RemoveParens<'a> {
self.visit_program(program);
}

fn strip_parenthesized_expression(self, expr: &mut Expression<'a>) {
fn strip_parenthesized_expression(&self, expr: &mut Expression<'a>) {
if let Expression::ParenthesizedExpression(paren_expr) = expr {
*expr = self.ast.move_expression(&mut paren_expr.expression);
self.strip_parenthesized_expression(expr);
Expand Down
77 changes: 77 additions & 0 deletions crates/oxc_minifier/src/ast_passes/replace_global_defines.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use oxc_allocator::Allocator;
use oxc_ast::{
ast::*,
// visit::walk_mut::{walk_expression_mut, walk_statements_mut},
AstBuilder,
VisitMut,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_parser::Parser;
use oxc_span::SourceType;

struct IdentifierDefine<'a> {
key: String,
value: Expression<'a>,
}

impl<'a> IdentifierDefine<'a> {
pub fn new(key: String, value: Expression<'a>) -> Self {
Self { key, value }
}
}

/// Replace Global Defines
///
/// References:
///
/// * <https://github.com/terser/terser?tab=readme-ov-file#conditional-compilation>
/// * <https://esbuild.github.io/api/#define>
pub struct ReplaceGlobalDefines<'a> {
ast: AstBuilder<'a>,
identifier_defines: Vec<IdentifierDefine<'a>>,
}

impl<'a> ReplaceGlobalDefines<'a> {
pub fn new(allocator: &'a Allocator) -> Self {
Self { ast: AstBuilder::new(allocator), identifier_defines: vec![] }
}

pub fn with_definitions<S>(mut self, key_values: &[(S, S)]) -> Result<Self, Vec<OxcDiagnostic>>
where
S: AsRef<str>,
{
for (key, value) in key_values.iter() {
self.add_definition(key.as_ref(), value.as_ref())?;
}
Ok(self)
}

fn add_definition(&mut self, key: &str, value: &str) -> Result<(), Vec<OxcDiagnostic>> {
// TODO: add dot defines

let source_text = self.ast.allocator.alloc(value.to_string());
let value = self.parse(source_text)?;
self.identifier_defines.push(IdentifierDefine::new(key.to_string(), value));
Ok(())
}

// TODO: add `parse_expression` to parser
fn parse(&self, source_text: &'a str) -> Result<Expression<'a>, Vec<OxcDiagnostic>> {
let mut ret = Parser::new(&self.ast.allocator, source_text, SourceType::default()).parse();
if !ret.errors.is_empty() {
return Err(ret.errors);
}
let Some(Statement::ExpressionStatement(expr_stmt)) = ret.program.body.get_mut(0) else {
return Err(vec![OxcDiagnostic::error(format!(
"Expression `{source_text}` cannot be parsed."
))]);
};
Ok(self.ast.move_expression(&mut expr_stmt.expression))
}

pub fn build(&mut self, program: &mut Program<'a>) {
self.visit_program(program);
}
}

impl<'a> VisitMut<'a> for ReplaceGlobalDefines<'a> {}
2 changes: 1 addition & 1 deletion crates/oxc_minifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use oxc_allocator::Allocator;
use oxc_ast::ast::Program;

pub use crate::{
ast_passes::{RemoveDeadCode, RemoveParens},
ast_passes::{RemoveDeadCode, RemoveParens, ReplaceGlobalDefines},
compressor::{CompressOptions, Compressor},
mangler::ManglerBuilder,
};
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_minifier/tests/oxc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod code_removal;
mod folding;
mod precedence;
mod remove_dead_code;
mod replace_global_defines;
24 changes: 24 additions & 0 deletions crates/oxc_minifier/tests/oxc/replace_global_defines.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use oxc_allocator::Allocator;
use oxc_codegen::WhitespaceRemover;
use oxc_minifier::ReplaceGlobalDefines;
use oxc_parser::Parser;
use oxc_span::SourceType;

fn minify(source_text: &str, defs: &[(&str, &str)]) -> String {
let source_type = SourceType::default();
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
ReplaceGlobalDefines::new(&allocator).with_definitions(defs.into()).unwrap().build(program);
WhitespaceRemover::new().build(program).source_text
}

pub(crate) fn test(source_text: &str, expected: &str, defs: &[(&str, &str)]) {
let minified = minify(source_text, defs);
assert_eq!(minified, expected, "for source {}", source_text);
}

#[test]
fn replace_global_definitions() {
test("id, str", "text, \"text\"", &[("id", "str"), ("id", "'str'")]);
}

0 comments on commit 3155895

Please sign in to comment.