use std::fs::{read_to_string, write};
use std::path::Path;
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_minifier::{Minifier, MinifierOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
use walkdir::WalkDir;
use crate::{Error, Result};
pub fn minify_str(source: &str, path: &Path) -> Result<String> {
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap_or_default();
let parsed = Parser::new(&allocator, source, source_type).parse();
if parsed.diagnostics.has_errors() {
let body = parsed
.diagnostics
.iter()
.map(|e| format!("{e:?}"))
.collect::<Vec<_>>()
.join("\n");
return Err(Error::Minify(format!(
"parse error(s) in {}:\n{body}",
path.display()
)));
}
let mut program = parsed.program;
let ret = Minifier::new(MinifierOptions::default()).minify(&allocator, &mut program);
let code = Codegen::new()
.with_options(CodegenOptions {
minify: true,
..CodegenOptions::default()
})
.with_scoping(ret.scoping)
.build(&program)
.code;
Ok(code)
}
pub fn minify_directory(dir: &Path) -> Result<usize> {
let mut count = 0;
for entry in WalkDir::new(dir)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|x| x == "js"))
{
let path = entry.path();
let source = read_to_string(path)?;
let minified = minify_str(&source, path)?;
write(path, minified)?;
count += 1;
}
Ok(count)
}
#[cfg(feature = "cli")]
crate::cli_config::feature_args!(
MinifyArgs,
minify,
"minify",
no_minify,
"no-minify",
crate::cli_config::NoConfig
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn folds_and_strips_whitespace() {
let min = minify_str("export const sum = 1 + 2;\n\n", Path::new("x.js")).unwrap();
assert!(min.contains('3'), "constant folded; got: {min}");
assert!(!min.contains(" = "), "whitespace stripped; got: {min}");
}
#[test]
fn minify_directory_processes_js_in_place() {
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("a.js");
write(&f, "export const sum = 1 + 2;\n").unwrap();
let n = minify_directory(dir.path()).unwrap();
assert_eq!(n, 1);
assert!(read_to_string(&f).unwrap().contains('3'));
}
}