移除 SSR HTML minify 中间件及相关工具
This commit is contained in:
parent
476fad27e5
commit
2c1190d8fb
@ -21,7 +21,6 @@ mod db;
|
|||||||
mod highlight;
|
mod highlight;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod middleware;
|
|
||||||
mod models;
|
mod models;
|
||||||
mod pages;
|
mod pages;
|
||||||
mod router;
|
mod router;
|
||||||
@ -104,11 +103,6 @@ fn main() {
|
|||||||
// 合并三条路由:自定义 API、静态资源、Dioxus 主应用
|
// 合并三条路由:自定义 API、静态资源、Dioxus 主应用
|
||||||
let router = api_routes.merge(static_routes).merge(dioxus_app);
|
let router = api_routes.merge(static_routes).merge(dioxus_app);
|
||||||
|
|
||||||
// 对 SSR 返回的 HTML 做空白压缩(保留 <pre>/<code> 等标签内格式)
|
|
||||||
let router = router.layer(axum::middleware::from_fn(
|
|
||||||
crate::middleware::minify_html::layer,
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(router)
|
Ok(router)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
//! SSR HTML 空白压缩中间件。
|
|
||||||
//!
|
|
||||||
//! 对 Dioxus fullstack 返回的 `text/html` 响应做轻量 minify。
|
|
||||||
//!
|
|
||||||
//! 注意:这里**不**维护 per-URL 缓存。同一个 URL 在 admin 登录态与匿名态下
|
|
||||||
//! 渲染出的 HTML 不同(例如是否带管理按钮),按 URL 缓存会导致跨用户内容
|
|
||||||
//! 泄露。Dioxus 的增量渲染器(ISRG)本身已经按 URL 缓存了 SSR HTML,
|
|
||||||
//! 这里的 minify 只是对其输出做一次无损空白折叠,无需再叠加一层缓存。
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
use axum::{
|
|
||||||
body::Body,
|
|
||||||
extract::Request,
|
|
||||||
http::{header, StatusCode},
|
|
||||||
middleware::Next,
|
|
||||||
response::Response,
|
|
||||||
};
|
|
||||||
use http_body_util::BodyExt;
|
|
||||||
|
|
||||||
/// Axum 中间件入口。
|
|
||||||
pub async fn layer(request: Request, next: Next) -> Response {
|
|
||||||
let response = next.run(request).await;
|
|
||||||
|
|
||||||
let is_html = response
|
|
||||||
.headers()
|
|
||||||
.get(header::CONTENT_TYPE)
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.map(|ct| ct.starts_with("text/html"))
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if !is_html {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (parts, body) = response.into_parts();
|
|
||||||
let bytes = match body.collect().await {
|
|
||||||
Ok(collected) => collected.to_bytes(),
|
|
||||||
Err(_) => {
|
|
||||||
return Response::builder()
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
.body(Body::empty())
|
|
||||||
.expect("failed to build error response");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let original = String::from_utf8_lossy(&bytes);
|
|
||||||
let minified = crate::utils::html_minify::minify_html(&original);
|
|
||||||
|
|
||||||
let mut response = Response::builder()
|
|
||||||
.status(parts.status)
|
|
||||||
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
|
||||||
.body(Body::from(minified.clone()))
|
|
||||||
.expect("failed to build minified response");
|
|
||||||
|
|
||||||
// 保留原响应的其他 header,并修正 Content-Length
|
|
||||||
*response.headers_mut() = parts.headers;
|
|
||||||
response.headers_mut().remove(header::TRANSFER_ENCODING);
|
|
||||||
response
|
|
||||||
.headers_mut()
|
|
||||||
.insert(header::CONTENT_LENGTH, minified.len().into());
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
#[cfg(feature = "server")]
|
|
||||||
pub mod minify_html;
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
//! HTML 空白压缩工具函数。
|
|
||||||
//!
|
|
||||||
//! 仅服务端使用。对 HTML 片段做轻量 minify:
|
|
||||||
//! - 合并标签之间连续空白为一个空格;
|
|
||||||
//! - 移除普通 HTML 注释,但**保留 Dioxus hydration marker 注释**;
|
|
||||||
//! - 保留 `<pre>`、`<code>`、`<textarea>`、`<script>`、`<style>` 内部原样空白。
|
|
||||||
//!
|
|
||||||
//! Dioxus 0.7 SSR 会在输出中插入若干 load-bearing 的注释 marker,客户端
|
|
||||||
//! hydration 依赖它们定位动态节点。删除这些注释会导致前端 hydration
|
|
||||||
//! 失败(节点对不齐、交互失效)。需要保留的格式:
|
|
||||||
//! `<!--#-->` 动态文本节点结束 marker
|
|
||||||
//! `<!--node-id<N>-->` 动态文本节点起始 marker
|
|
||||||
//! `<!--placeholder-->` / `<!--placeholder<N>-->` 占位节点 marker
|
|
||||||
//! (参考 others/dioxus/packages/ssr/src/renderer.rs)
|
|
||||||
|
|
||||||
use lol_html::{doc_comments, doc_text, element, rewrite_str, RewriteStrSettings};
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
/// 判断一段注释文本是否为 Dioxus hydration marker,必须原样保留。
|
|
||||||
fn is_hydration_marker(comment_text: &str) -> bool {
|
|
||||||
comment_text == "#"
|
|
||||||
|| comment_text.starts_with("node-id")
|
|
||||||
|| comment_text.starts_with("placeholder")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 压缩 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| {
|
|
||||||
// 只删除非 hydration marker 的普通注释。
|
|
||||||
if !is_hydration_marker(c.text().as_str()) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
//! 临时 bench:测量 minify_html 对真实大小 HTML 的耗时。
|
|
||||||
//! 用完即删。
|
|
||||||
|
|
||||||
#[cfg(all(test, feature = "server"))]
|
|
||||||
mod bench {
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
fn synthetic_html(size_hint: usize) -> String {
|
|
||||||
// 构造一个带空白、注释、嵌套标签的模拟 HTML,体积接近真实 SSR 输出。
|
|
||||||
let unit = "<div class=\"x\">\n <p>hello world</p>\n <!-- comment -->\n <a href=\"/p\">link</a>\n</div>\n";
|
|
||||||
let unit_len = unit.len();
|
|
||||||
let repeats = size_hint / unit_len + 1;
|
|
||||||
unit.repeat(repeats)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bench_minify_throughput() {
|
|
||||||
for &kb in &[50usize, 200, 568] {
|
|
||||||
let html = synthetic_html(kb * 1024);
|
|
||||||
// 预热
|
|
||||||
let _ = crate::utils::html_minify::minify_html(&html);
|
|
||||||
|
|
||||||
let n = 20;
|
|
||||||
let start = Instant::now();
|
|
||||||
for _ in 0..n {
|
|
||||||
let _ = crate::utils::html_minify::minify_html(&html);
|
|
||||||
}
|
|
||||||
let elapsed = start.elapsed() / n;
|
|
||||||
let input_kb = html.len() as f64 / 1024.0;
|
|
||||||
let mbps = (html.len() as f64 / elapsed.as_secs_f64()) / (1024.0 * 1024.0);
|
|
||||||
println!(
|
|
||||||
"input={:.0}KB avg minify={:?} (~{:.0} MB/s)",
|
|
||||||
input_kb,
|
|
||||||
elapsed,
|
|
||||||
mbps
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,9 +7,3 @@
|
|||||||
pub mod text;
|
pub mod text;
|
||||||
/// 跨平台的异步睡眠等时间工具。
|
/// 跨平台的异步睡眠等时间工具。
|
||||||
pub mod time;
|
pub mod time;
|
||||||
/// HTML 空白压缩工具。
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
pub mod html_minify;
|
|
||||||
|
|
||||||
#[cfg(all(test, feature = "server"))]
|
|
||||||
mod html_minify_bench;
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user