feat: add CSS minification via lightningcss
- Cargo.toml: Add lightningcss 1.0.0-alpha.70 dep - src/css.rs: New module with minify_css() function. Uses StyleSheet::parse() + minify() + to_css() pipeline. 3 unit tests: whitespace removal, comment removal, selector merge. - src/main.rs: Integrate minification into copy_static_assets(). CSS files minified before writing; size delta logged. Result: style.css 5670→4165 bytes (~27% smaller)
This commit is contained in:
80
src/css.rs
Normal file
80
src/css.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! CSS processing via lightningcss.
|
||||
|
||||
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions, StyleSheet};
|
||||
|
||||
/// Minify CSS content.
|
||||
///
|
||||
/// Returns minified CSS string on success, or the original input on error.
|
||||
pub fn minify_css(css: &str) -> String {
|
||||
match try_minify(css) {
|
||||
Ok(minified) => minified,
|
||||
Err(_) => css.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_minify(css: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut stylesheet = StyleSheet::parse(css, ParserOptions::default())
|
||||
.map_err(|e| format!("parse error: {:?}", e))?;
|
||||
|
||||
stylesheet
|
||||
.minify(MinifyOptions::default())
|
||||
.map_err(|e| format!("minify error: {:?}", e))?;
|
||||
|
||||
let result = stylesheet
|
||||
.to_css(PrinterOptions {
|
||||
minify: true,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|e| format!("print error: {:?}", e))?;
|
||||
|
||||
Ok(result.code)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_minify_removes_whitespace() {
|
||||
let input = r#"
|
||||
.foo {
|
||||
color: red;
|
||||
}
|
||||
"#;
|
||||
let output = minify_css(input);
|
||||
|
||||
// Should be smaller (whitespace removed)
|
||||
assert!(output.len() < input.len());
|
||||
// Should still contain the essential content
|
||||
assert!(output.contains(".foo"));
|
||||
assert!(output.contains("color"));
|
||||
assert!(output.contains("red"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_minify_removes_comments() {
|
||||
let input = r#"
|
||||
/* This is a comment */
|
||||
.bar { background: blue; }
|
||||
"#;
|
||||
let output = minify_css(input);
|
||||
|
||||
// Comment should be removed
|
||||
assert!(!output.contains("This is a comment"));
|
||||
// Rule should remain
|
||||
assert!(output.contains(".bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_minify_merges_selectors() {
|
||||
let input = r#"
|
||||
.foo { color: red; }
|
||||
.bar { color: red; }
|
||||
"#;
|
||||
let output = minify_css(input);
|
||||
|
||||
// Should merge identical rules
|
||||
// Either ".foo,.bar" or ".bar,.foo" pattern
|
||||
assert!(output.contains(","));
|
||||
}
|
||||
}
|
||||
37
src/main.rs
37
src/main.rs
@@ -3,6 +3,7 @@
|
||||
//! Transforms markdown content into a minimal static site.
|
||||
|
||||
mod content;
|
||||
mod css;
|
||||
mod error;
|
||||
mod highlight;
|
||||
mod render;
|
||||
@@ -214,8 +215,11 @@ fn write_output(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy static assets (CSS, etc.) to output directory
|
||||
/// Copy static assets (CSS, images, etc.) to output directory.
|
||||
/// CSS files are minified before writing.
|
||||
fn copy_static_assets(static_dir: &Path, output_dir: &Path) -> Result<()> {
|
||||
use crate::css::minify_css;
|
||||
|
||||
if !static_dir.exists() {
|
||||
return Ok(()); // No static dir is fine
|
||||
}
|
||||
@@ -241,12 +245,31 @@ fn copy_static_assets(static_dir: &Path, output_dir: &Path) -> Result<()> {
|
||||
})?;
|
||||
}
|
||||
|
||||
fs::copy(src, &dest).map_err(|e| Error::WriteFile {
|
||||
path: dest.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
eprintln!("copying: {} → {}", src.display(), dest.display());
|
||||
// Minify CSS files, copy others directly
|
||||
if src.extension().is_some_and(|ext| ext == "css") {
|
||||
let css = fs::read_to_string(src).map_err(|e| Error::ReadFile {
|
||||
path: src.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
let minified = minify_css(&css);
|
||||
fs::write(&dest, &minified).map_err(|e| Error::WriteFile {
|
||||
path: dest.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
eprintln!(
|
||||
"minifying: {} → {} ({} → {} bytes)",
|
||||
src.display(),
|
||||
dest.display(),
|
||||
css.len(),
|
||||
minified.len()
|
||||
);
|
||||
} else {
|
||||
fs::copy(src, &dest).map_err(|e| Error::WriteFile {
|
||||
path: dest.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
eprintln!("copying: {} → {}", src.display(), dest.display());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user