This commit is contained in:
gru committed 2026-10-03 00:27:09 +01:00
commit 5e23e9af02
240 files changed
+99957

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
use anyhow::{Context, Result};
/// Conservative JavaScript minification without token rewriting.
///
/// JavaScript `/` is context-sensitive (division vs. RegExp literal), so this
/// minifier never guesses. Outside strings/templates/comments it collapses
/// horizontal whitespace only until the first slash on a physical line; the
/// remainder of that line is copied verbatim. This still removes common
/// indentation/alignment whitespace while preserving RegExp bodies, ASI-sensitive
/// line terminators and comment text exactly.
pub fn minify(body: &[u8]) -> Result<Vec<u8>> {
let input = std::str::from_utf8(body).context("JavaScript is not UTF-8")?;
let mut out = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
let mut quote: Option<char> = None;
let mut block_comment = false;
let mut line_comment = false;
let mut slash_sensitive = false;
let mut horizontal_space = false;
while let Some(ch) = chars.next() {
if line_comment {
out.push(ch);
if ch == '\n' || ch == '\r' {
line_comment = false;
slash_sensitive = false;
horizontal_space = false;
}
continue;
}
if block_comment {
out.push(ch);
if ch == '*' && chars.peek() == Some(&'/') {
chars.next();
out.push('/');
block_comment = false;
}
if ch == '\n' || ch == '\r' {
slash_sensitive = false;
}
continue;
}
if let Some(active) = quote {
out.push(ch);
if ch == '\\' {
if let Some(next) = chars.next() {
out.push(next);
}
} else if ch == active {
quote = None;
}
continue;
}
if slash_sensitive {
out.push(ch);
if ch == '\n' || ch == '\r' {
slash_sensitive = false;
horizontal_space = false;
}
continue;
}
if matches!(ch, '\'' | '"' | '`') {
if horizontal_space {
out.push(' ');
horizontal_space = false;
}
quote = Some(ch);
out.push(ch);
continue;
}
if ch == '/' {
if horizontal_space {
out.push(' ');
horizontal_space = false;
}
out.push(ch);
match chars.peek() {
Some(&'/') => {
chars.next();
out.push('/');
line_comment = true;
}
Some(&'*') => {
chars.next();
out.push('*');
block_comment = true;
}
_ => slash_sensitive = true,
}
continue;
}
if matches!(ch, ' ' | '\t' | '\u{000b}' | '\u{000c}') {
horizontal_space = true;
continue;
}
if ch == '\r' || ch == '\n' {
horizontal_space = false;
slash_sensitive = false;
out.push(ch);
continue;
}
if horizontal_space {
out.push(' ');
horizontal_space = false;
}
out.push(ch);
}
let trimmed_len = out.trim_end_matches([' ', '\t']).len();
out.truncate(trimmed_len);
Ok(out.into_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keeps_regex_literals_and_asi_sensitive_newlines() {
let input = b"const r = /a b[/*]/; // keep\nreturn value;\n";
let output = String::from_utf8(minify(input).unwrap()).unwrap();
assert!(output.contains("/a b[/*]/; // keep"));
assert!(output.contains("return value"));
}
#[test]
fn keeps_template_literal_whitespace() {
let input = b"const x = `a b ${ value }`;\n";
let output = String::from_utf8(minify(input).unwrap()).unwrap();
assert_eq!(output, "const x = `a b ${ value }`;\n");
}
}