yggdrasil/src/utils/html_minify.rs
xfy bd659c5b4f
Some checks failed
CI / check (push) Failing after 5m54s
CI / build (push) Has been skipped
perf: add HTML/CSS minification for SSR responses and highlight CSS
- Add lol_html-based HTML whitespace minifier (utils/html_minify.rs)
  that collapses whitespace and strips comments while preserving
  <pre>, <code>, <textarea>, <script>, <style> content
- Add Axum middleware (middleware/minify_html.rs) that minifies
  text/html responses with per-URL moka cache (256 entries, 300s TTL)
- Wire middleware into the server router in main.rs
- Add CSS minifier to generate_highlight_css build step
- Apply minify_html to rendered markdown output and TOC
- Add http-body-util dependency for body collection in middleware
2026-06-17 09:49:44 +08:00

76 lines
2.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! HTML 空白压缩工具函数。
//!
//! 仅服务端使用。对 HTML 片段做轻量 minify
//! - 合并标签之间连续空白为一个空格;
//! - 移除 HTML 注释;
//! - 保留 `<pre>`、`<code>`、`<textarea>`、`<script>`、`<style>` 内部原样空白。
use lol_html::{doc_comments, doc_text, element, rewrite_str, RewriteStrSettings};
use std::cell::Cell;
use std::rc::Rc;
/// 压缩 HTML 中的无用空白。
pub fn minify_html(input: &str) -> String {
let protected_depth: Rc<Cell<usize>> = Rc::new(Cell::new(0));
rewrite_str(
input,
RewriteStrSettings {
element_content_handlers: vec![element!(
"pre, code, textarea, script, style",
{
let depth = protected_depth.clone();
move |el| {
depth.set(depth.get() + 1);
let depth_end = depth.clone();
let _ = el.on_end_tag(lol_html::end_tag!(move |_end| {
depth_end.set(depth_end.get().saturating_sub(1));
Ok(())
}));
Ok(())
}
}
)],
document_content_handlers: vec![
doc_text!({
let depth = protected_depth.clone();
move |text| {
if depth.get() == 0 {
let s = text.as_str();
let collapsed = collapse_whitespace(s);
if collapsed != s {
text.set_str(collapsed);
}
}
Ok(())
}
}),
doc_comments!(|c| {
c.remove();
Ok(())
}),
],
..RewriteStrSettings::default()
},
)
.unwrap_or_else(|_| input.to_string())
}
/// 将连续空白字符合并为一个空格。
fn collapse_whitespace(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut prev_ws = false;
for ch in s.chars() {
if ch.is_whitespace() {
if !prev_ws {
out.push(' ');
prev_ws = true;
}
} else {
out.push(ch);
prev_ws = false;
}
}
out
}