你搭了个 VitePress 文档站,构建完打开 DevTools 一看,HTML 响应里全是缩进和换行,明明 CSS 和 JS 都已经被压缩过了,为什么 HTML 还是这么臃肿?
VitePress 默认只压缩 CSS 和 JS 资源,Vue 渲染出来的页面主体部分还算紧凑,但剩下的 HTML 模板部分——也就是那些静态的骨架标签、导航、页脚——并不会被压缩。在 VitePress 1.6.4 上实测,这部分 HTML 是原样输出的。对于内容型站点,HTML 体积虽然通常不大,但积少成多,尤其在高并发访问或弱网环境下,每一字节都值得抠。
解决思路很直接:在构建阶段给 HTML 加一道压缩工序。作者推荐的是 HTML Minifier Next(HMN),一个活跃维护的 HTML 压缩器,压缩效果在同类工具中表现突出。接入只需要两步,非常轻量。
第一步,安装依赖。在项目里以开发依赖方式安装:`npm i -D html-minifier-next`。
第二步,在 VitePress 配置文件(`.vitepress/config.mts` 或类似文件)里挂一个钩子。VitePress 提供了 `transformHtml` 这个构建钩子,会在每个页面 HTML 生成后、写入磁盘前被调用。在这里调用 HMN 的 `minify` 函数即可:
```ts
import { minify } from 'html-minifier-next';
// 在 defineConfig 块内
export default defineConfig({
async transformHtml(code) {
return minify(code, {
preset: 'comprehensive',
removeComments: false // Vue 需要注释标记来完成水合
});
}
});
```
这里有个关键细节:`removeComments` 必须设为 `false`。原因是 Vue 在服务端渲染时会在 HTML 里插入类似 `<!--[-->` 和 `<!--]-->` 这样的注释标记,用来在客户端水合(hydration,即浏览器端接管页面并绑定事件的过程)时匹配服务端渲染的 DOM 结构。如果把这些注释删掉,水合就会失败,页面会出现闪烁或事件绑定错乱。好在 Vue 在生产构建时本来就会剥离其他所有注释,所以保留这两个标记没有任何额外负担。
另一个容易踩的坑是:如果 Markdown 里嵌入了 Vue 组件或 `v-if` 这类指令,且文本内容带有首尾空白,压缩时可能会改变空白结构导致水合不匹配。这时候需要额外加上 `conservativeCollapse: true` 选项,让压缩器保守地折叠空白,避免破坏 Vue 的匹配逻辑。
配置里用的是 HMN 的 `comprehensive` 预设,压缩效果比较激进,适合追求极致体积的场景。如果担心某些边界情况,也可以换成 `conservative` 预设,或者完全手动指定压缩选项,按需定制。
还有一点值得注意:`transformHtml` 只在 `vitepress build` 时执行,开发模式(`vitepress dev`)下的 HTML 保持原样,不会影响本地调试体验。
这个方案最让我欣赏的地方在于它的克制——没有引入复杂的构建插件,没有改写 VitePress 的渲染流程,只是在一个官方提供的钩子里调用一个成熟的库,两行代码就完成了优化。对于任何基于 VitePress 的文档站、博客或产品官网,这几乎是无脑可用的优化手段。
如果你也在维护 VitePress 站点,不妨现在就试一下。安装依赖、加一段配置、重新构建,对比一下 HTML 体积的变化。对于内容密集型页面,压缩率通常相当可观。唯一需要留意的是,如果你的 Markdown 里大量使用 Vue 组件且对空白敏感,记得加上 `conservativeCollapse`,并在构建后抽查几个页面确认水合正常。
顺带一提,HMN 本身也支持压缩 CSS 和 JS,如果你有更精细的优化需求,可以把它扩展到其他资源类型上。不过对大多数 VitePress 项目来说,默认的 CSS/JS 压缩加上这层 HTML 压缩,已经足够让产物变得非常干净了。
内容与图片版权归原作者所有 · 原文: https://meiert.com/blog/web-page-minification-with-vitepress/