use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_minifier::{Minifier, MinifierOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
use super::{Asset, Context, MediaType, ProcessesAssets, ProcessingError};
pub struct MinifyJsProcessor;
impl ProcessesAssets for MinifyJsProcessor {
fn process(&self, _context: &mut Context, asset: &mut Asset) -> Result<(), ProcessingError> {
if asset.media_type() != &MediaType::JavaScript {
return Ok(());
}
if asset.path().ends_with(".min.js") {
return Ok(());
}
tracing::trace!("minify_js: {}", asset.path());
let source = asset.as_text()?;
let allocator = Allocator::default();
let source_type = SourceType::mjs();
let ret = Parser::new(&allocator, source, source_type).parse();
if !ret.errors.is_empty() {
let error_messages: Vec<_> = ret.errors.iter().map(|e| e.to_string()).collect();
return Err(ProcessingError::Compilation {
message: format!("JS parse errors: {}", error_messages.join("; ")).into(),
});
}
let mut program = ret.program;
let options = MinifierOptions::default();
Minifier::new(options).minify(&allocator, &mut program);
let output = Codegen::new()
.with_options(CodegenOptions::minify())
.build(&program);
asset.replace_with_text(output.code.into(), MediaType::JavaScript);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minifies_js() {
let js = r#"
// This is a comment
function hello(name) {
/* Another comment */
console.log("Hello, " + name);
}
hello("world");
"#;
let mut asset = Asset::new("script.js".into(), js.as_bytes().to_vec());
MinifyJsProcessor
.process(&mut Context::default(), &mut asset)
.unwrap();
let result = asset.as_text().unwrap();
assert!(!result.contains("// This is a comment"));
assert!(!result.contains("/* Another comment */"));
assert!(result.contains("Hello, "));
assert!(result.contains("console.log"));
assert!(result.len() < js.len());
}
#[test]
fn skips_non_js() {
let mut asset = Asset::new("index.html".into(), b"<html></html>".to_vec());
MinifyJsProcessor
.process(&mut Context::default(), &mut asset)
.unwrap();
assert_eq!(asset.as_text().unwrap(), "<html></html>");
}
#[test]
fn skips_already_minified() {
let js = "function test(){console.log('already minified')}";
let mut asset = Asset::new("vendor.min.js".into(), js.as_bytes().to_vec());
MinifyJsProcessor
.process(&mut Context::default(), &mut asset)
.unwrap();
assert_eq!(asset.as_text().unwrap(), js);
}
}