Menu
Public repository / 9df51040bf1687e4dc8a91557f02df52e8d5cf3a
AkurAI-Framework / crates/assets/src/minify.rs
Latest change 9df51040bf1687e4dc8a91557f02df52e8d5cf3a - feat(assets): content-hash fingerprinting + conservative minifiers by Ólafur Búi Ólafsson
//! Conservative, correctness-first minifiers for CSS and JS.
//!
//! These trade ratio for safety. They never reorder, rename, or rewrite code;
//! they only remove bytes that are provably insignificant (comments, redundant
//! whitespace). String, `url(...)`, and template-literal contents are passed
//! through verbatim so values are never corrupted.
/// Structural CSS punctuation around which surrounding whitespace is redundant.
fn is_struct(c: char) -> bool {
matches!(c, '{' | '}' | ':' | ';' | ',')
}
/// Is the last emitted byte a structural CSS char? (All such chars are ASCII,
/// so a trailing UTF-8 continuation byte can never be mistaken for one.)
fn last_is_struct(out: &str) -> bool {
matches!(
out.as_bytes().last(),
Some(b'{' | b'}' | b':' | b';' | b',')
)
}
/// Resolve one run of collapsed whitespace immediately before the next token.
///
/// The space is dropped at the start of output, or when either side is a
/// structural char; otherwise a single space is kept (so multi-value
/// declarations like `0 24px 60px` survive intact).
fn flush_ws(out: &mut String, pending: &mut bool, next_struct: bool) {
if *pending {
*pending = false;
if !out.is_empty() && !next_struct && !last_is_struct(out) {
out.push(' ');
}
}
}
/// Is `c` part of a CSS identifier (used for `url(` word-boundary detection)?
fn is_ident(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '-' || c == '_'
}
/// Does `chars[i..]` start a `url(` token at an identifier boundary?
fn at_url(chars: &[char], i: usize) -> bool {
if i + 4 > chars.len() {
return false;
}
let m = chars[i].eq_ignore_ascii_case(&'u')
&& chars[i + 1].eq_ignore_ascii_case(&'r')
&& chars[i + 2].eq_ignore_ascii_case(&'l')
&& chars[i + 3] == '(';
m && (i == 0 || !is_ident(chars[i - 1]))
}
/// Conservatively minify CSS.
///
/// - Strips `/* … */` comments.
/// - Collapses runs of whitespace to a single space, then drops that space
/// where it is redundant (next to `{ } : ; ,` or at the edges).
/// - Drops a `;` immediately before `}` (the trailing semicolon is optional).
/// - **Never** touches the contents of string literals (`"…"`, `'…'`) or
/// `url(…)` — data URIs, shadows, and quoted values are preserved byte for
/// byte.
///
/// ```
/// use akurai_assets::minify_css;
/// assert_eq!(
/// minify_css(".box {\n margin: 0 24px 60px;\n}"),
/// ".box{margin:0 24px 60px}"
/// );
/// ```
pub fn minify_css(input: &str) -> String {
let chars: Vec<char> = input.chars().collect();
let n = chars.len();
let mut out = String::with_capacity(input.len());
let mut pending = false;
let mut i = 0;
while i < n {
let c = chars[i];
// Comment: /* ... */ → acts as a whitespace separator.
if c == '/' && i + 1 < n && chars[i + 1] == '*' {
i += 2;
while i + 1 < n && !(chars[i] == '*' && chars[i + 1] == '/') {
i += 1;
}
i = (i + 2).min(n); // skip the closing */ (clamp if unterminated)
pending = true;
continue;
}
// Whitespace: collapse; decide later whether to keep one space.
if c.is_whitespace() {
pending = true;
i += 1;
continue;
}
// String literal: copy verbatim (respecting backslash escapes).
if c == '"' || c == '\'' {
flush_ws(&mut out, &mut pending, false);
out.push(c);
i += 1;
while i < n {
let d = chars[i];
out.push(d);
if d == '\\' && i + 1 < n {
out.push(chars[i + 1]);
i += 2;
continue;
}
i += 1;
if d == c {
break;
}
}
continue;
}
// url(...) : copy contents verbatim so data URIs aren't corrupted.
if at_url(&chars, i) {
flush_ws(&mut out, &mut pending, false);
out.push_str("url(");
i += 4;
let mut depth = 1usize;
while i < n && depth > 0 {
let d = chars[i];
if d == '"' || d == '\'' {
out.push(d);
i += 1;
while i < n {
let e = chars[i];
out.push(e);
if e == '\\' && i + 1 < n {
out.push(chars[i + 1]);
i += 2;
continue;
}
i += 1;
if e == d {
break;
}
}
continue;
}
if d == '(' {
depth += 1;
} else if d == ')' {
depth -= 1;
}
out.push(d);
i += 1;
}
continue;
}
// Ordinary char.
flush_ws(&mut out, &mut pending, is_struct(c));
if c == '}' && out.ends_with(';') {
out.pop();
}
out.push(c);
i += 1;
}
out
}
/// Conservatively minify JS — a deliberately minimal, semantics-preserving pass.
///
/// - Strips `// line` and `/* block */` comments.
/// - Trims trailing whitespace and drops blank lines.
/// - Does **not** reorder, rename, join lines, or collapse intra-line
/// whitespace, so Automatic Semicolon Insertion is unaffected.
/// - String (`'…'`, `"…"`) and template-literal (`` `…` ``) contents are copied
/// verbatim, so a `//` or `/* */` *inside* a string is preserved.
///
/// ## Limitations (by design — correctness over ratio)
/// - **No regex-literal awareness.** A `/.../ ` regex containing `//` or `/*`
/// could be misread as a comment. This is rare in practice; for full safety
/// run this only on code without such regex literals, or pre-minified output.
/// - **No `${…}` interpolation parsing.** A template literal is opaque up to its
/// closing backtick; a backtick nested inside `${ }` is an edge case left
/// untouched.
///
/// ```
/// use akurai_assets::minify_js;
/// assert_eq!(minify_js("let a = 1; // a comment"), "let a = 1;");
/// assert_eq!(minify_js("let s = \"http://x\"; // c"), "let s = \"http://x\";");
/// ```
pub fn minify_js(input: &str) -> String {
let chars: Vec<char> = input.chars().collect();
let n = chars.len();
let mut out = String::with_capacity(input.len());
let mut i = 0;
while i < n {
let c = chars[i];
// String / template literal: copy verbatim (respecting escapes).
if c == '"' || c == '\'' || c == '`' {
out.push(c);
i += 1;
while i < n {
let d = chars[i];
out.push(d);
if d == '\\' && i + 1 < n {
out.push(chars[i + 1]);
i += 2;
continue;
}
i += 1;
if d == c {
break;
}
}
continue;
}
// Line comment: drop to end of line (leave the newline).
if c == '/' && i + 1 < n && chars[i + 1] == '/' {
i += 2;
while i < n && chars[i] != '\n' {
i += 1;
}
continue;
}
// Block comment: drop through the closing */.
if c == '/' && i + 1 < n && chars[i + 1] == '*' {
i += 2;
while i + 1 < n && !(chars[i] == '*' && chars[i + 1] == '/') {
i += 1;
}
i = (i + 2).min(n);
continue;
}
out.push(c);
i += 1;
}
// Trim trailing whitespace per line and drop blank lines.
let mut result = String::with_capacity(out.len());
for line in out.split('\n') {
let trimmed = line.trim_end();
if trimmed.is_empty() {
continue;
}
if !result.is_empty() {
result.push('\n');
}
result.push_str(trimmed);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn css_strips_comments_and_collapses_space() {
let css = "/* header */ .box {\n color: red;\n}";
assert_eq!(minify_css(css), ".box{color:red}");
}
#[test]
fn css_preserves_multi_value_declarations() {
let css = ".s {\n margin: 0 24px 60px;\n box-shadow: 0 24px 60px rgba(0,0,0,.5);\n}";
assert_eq!(
minify_css(css),
".s{margin:0 24px 60px;box-shadow:0 24px 60px rgba(0,0,0,.5)}"
);
}
#[test]
fn css_drops_trailing_semicolon_before_brace() {
assert_eq!(minify_css(".a{color:red ;}"), ".a{color:red}");
}
#[test]
fn css_preserves_url_contents() {
let css = ".b { background: url(data:image/png;base64,AB,C==) ; }";
assert_eq!(
minify_css(css),
".b{background:url(data:image/png;base64,AB,C==)}"
);
}
#[test]
fn css_preserves_string_contents() {
let css = ".c::before { content: \"a; b : c\"; }";
assert_eq!(minify_css(css), ".c::before{content:\"a; b : c\"}");
}
#[test]
fn js_removes_line_comment() {
assert_eq!(minify_js("let a = 1; // a comment"), "let a = 1;");
}
#[test]
fn js_removes_full_comment_line_and_block() {
let js = "// header\nconst x = 1;\n/* block\n spanning */\nf(x);";
assert_eq!(minify_js(js), "const x = 1;\nf(x);");
}
#[test]
fn js_preserves_string_with_double_slash() {
let js = "const u = \"http://example.com\"; // real comment";
let out = minify_js(js);
assert_eq!(out, "const u = \"http://example.com\";");
assert!(out.contains("//example.com"));
}
#[test]
fn js_preserves_block_comment_marker_in_string() {
let js = "const g = '/* not a comment */';";
assert_eq!(minify_js(js), "const g = '/* not a comment */';");
}
#[test]
fn js_collapses_blank_lines() {
assert_eq!(minify_js("a();\n\n\n \nb();"), "a();\nb();");
}
}