前端预压缩
约 3440 字大约 11 分钟
2025-06-29
前端预压缩
前端预压缩指的是在前端构建阶段对静态资源进行压缩,生成压缩后的文件,以减少传输数据量,提升页面加载速度。服务器在收到请求时,根据请求头的 Accept-Encoding
字段判断是否支持压缩,并返回对应的压缩文件。浏览器在收到压缩文件后,对文件进行解压缩并执行。
预压缩的三大特征
1、构建阶段完成压缩
在 npm run build
时生成压缩后的 .gz
、.br
文件,而非用户请求时压缩
2、产物包含压缩文件
输出目录中同时存在压缩文件和原始文件,例如 main.js
、main.js.gz
、main.js.br
dist/
├─ main.js
├─ main.js.gz # 预压缩生成
├─ main.js.br # 预压缩生成
├─ style.css
├─ style.css.gz
├─ style.css.br
3、服务器直接使用
Nginx 等服务器只需配置gzip_static on
即可优先使用已生成的预压缩文件
http {
gzip off; # 禁用动态压缩
gzip_static on; # 优先使用预压缩文件(如果有)
}
工作流程
浏览器发起请求
浏览器发起请求时,在请求头中设置属性
accept-encoding:gzip
。表明浏览器支持 gzip 压缩,这个参数是浏览器自动会携带的请求头信息。服务器收到浏览器发送的请求之后:
- 返回压缩后的文件,并在响应头中设置
content-encoding: gzip
; - 如果没有压缩文件,返回未压缩的请求文件。
- 返回压缩后的文件,并在响应头中设置
浏览器处理
浏览器接收到到服务器的响应,根据
content-encoding: gzip
响应头使用gzip
策略去解压压缩后的资源,通过content-type
内容类型决定怎么编码读取该文件内容。结束
与其他压缩方案的对比
传统的实时压缩方案,需要在服务器上配置 Gzip 模块,且每个请求都需要实时压缩,这样的操作比较考验服务器的配置和性能。
Webpack 预压缩方案,在构建阶段一次性完成压缩,服务器无需额外处理,直接配置相关参数,返回对应的 .gz
文件。
对比维度 | 前端预压缩 | 服务器实时压缩 | CDN 边缘压缩 |
---|---|---|---|
压缩时机 | 构建阶段(如 Webpack / Vite) | 处理请求时,实时压缩(Nginx / Apache) | CDN 节点首次回源时,压缩并缓存 |
性能消耗 | ❌ 构建时间增加 | ✅ 实时 CPU 消耗 | ✅ 仅首次回源消耗,后续请求直接返回压缩文件 |
压缩算法 | Brotli + Gzip(兼容性) | Brotli + Gzip(兼容性) | 支持 Gzip、Brotli(取决于 CDN 配置) |
延迟影响 | ⚡️ 零延迟(文件已预压缩) | ⏳ 增加 5-50ms 压缩时间延迟 | ⏳ 仅首次请求有延迟 |
存储空间 | 需额外存储 .gz / .br 文件 | 不占额外存储 | 由 CDN 自动管理 |
适用场景 | Web 静态资源(JS/CSS/图片) | 动态内容(API 响应/SSR 页面) | 全球加速的静态资源 |
配置复杂度 | ⚠️ 需要在构建工具配置压缩插件 | ⚠️ 需服务器配置一些参数(如gzip on ) | ✅ CDN 控制台一键开启 |
典型工具 | Webpack 的 compression-webpack-plugin | Nginx 的 gzip / brotli 模块 | Cloudflare / AWS CloudFront 等 CDN 服务 |
缓存效率 | ✅ 最高(内容哈希命名) | ⚠️ 依赖 ETag / Last-Modified | ✅ 边缘节点缓存压缩版本 |
兼容性 | 需服务端配置正确 Content-Encoding | 依赖客户端 Accept-Encoding 支持 | 自动适配客户端支持的算法 |
压缩算法的选择
Gzip 压缩
Gzip 是一种广泛使用的压缩算法,被广泛用于 HTTP 协议中。它通过对文件内容进行 无损压缩,并使用 LZ77 + 哈夫曼编码 来实现。
主要特点:
- 跨平台支持:所有主流操作系统(Linux/macOS/Windows)和编程语言均原生支持
- Web 标准:HTTP 协议默认支持的压缩方式(Content-Encoding: gzip)
- 工具链完善:gzip/gunzip 命令行工具、zlib 库等生态成熟
- 适合 Web 传输、日志归档、数据库备份等场景
优势:
- 兼容性无敌:所有系统/语言原生支持
- 内存占用极低:适合资源受限环境
- 解压速度极快
劣势:
- 压缩比和压缩速度均落后于现代算法
- 不支持多文件压缩(需配合 tar)
Brotli 压缩
Brotli 是 Google 开发的一种新的压缩算法,它在压缩效率和压缩速度之间取得了平衡。与 Gzip 相比,Brotli 提供更高的压缩率,但压缩速度较慢。
主要特点:
- 超高压缩率(核心优势),比 Gzip 高 20-26%压缩率,典型文本资源对比:
- 内置静态字典:包含 13,184 个 Web 常用词(如
<div>
、function()
等),覆盖 50%+的 Web 文本模式 - 预训练模型:针对 HTML/CSS/JS 语法优化,对 React/Vue 等框架的模板压缩效果极佳
- HTTP 协议原生支持
- TLS 友好,更小的体积减少 HTTPS 握手后的数据传输量
- 适用场景:适合需要高压缩比的场景,如网页内容、大型数据集等。
优势:
- Web 专项优化:内置静态字典,压缩比最高
- 渐进式解码:适合流式传输
- HTTPS 友好:减少 TLS 数据传输量
劣势:
- 压缩速度极慢(尤其高级别)
- 内存占用高
Zstandard 压缩
Zstandard(简称 Zstd)是 Facebook 于 2016 年开源的一种现代高性能压缩算法,旨在提供接近实时压缩速度与高压缩率的完美平衡。相比传统算法(如 Gzip、Brotli),它在速度、压缩比和资源消耗之间实现了显著突破,已成为数据库、游戏、日志处理等领域的首选压缩方案。
主要特点:
- 压缩率:通常比 gzip 稍低,但比 Brotli 高。
- 压缩速度:非常快。
- 解压速度:同样非常快。
- 适用场景:适合需要高速压缩和解压的场景,如日志处理、实时数据传输等。
优势:
- 速度与压缩比平衡:实时压缩能力极强
- 字典训练:支持预训练字典优化特定数据类型
- 多线程压缩:充分利用多核 CPU
劣势:
- 最高压缩比略低于 Brotli
- 旧版本兼容性一般(需 zstd ≥ v1.4+)
压缩算法的对比
1、关键技术差异
技术点 | Zstd | Gzip | Brotli |
---|---|---|---|
核心算法 | LZ77 + FSE | LZ77 + Huffman | LZ77 + 静态字典 + 上下文建模 |
字典支持 | ✅ 可训练字典 | ❌ 无 | ✅ 内置静态字典 |
块分割 | 动态(128KB-4MB) | 固定 32KB 窗口 | 动态(16KB-16MB) |
HTTP 支持 | 需手动配置 | 默认支持 | 主流浏览器/服务器支持 |
2、核心指标对比
指标 | Zstd (级别 3) | Gzip (级别 6) | Brotli (级别 11) |
---|---|---|---|
压缩速度 | 520 MB/s | 120 MB/s | 12 MB/s |
解压速度 | 1800 MB/s | 500 MB/s | 400 MB/s |
压缩比 | 3.5:1 | 3.2:1 | 4.3:1 |
内存占用 | ~16 MB | <10 MB | ~300 MB |
压缩级别范围 | -1 到 22 | 1 到 9 | 0 到 11 |
多线程支持 | ✅ 是 | ❌ 否 | ❌ 否 |
测试数据参考:混合文本/二进制,i9-13900K CPU
3、适用场景推荐
场景 | 推荐算法 | 理由 |
---|---|---|
数据库存储/日志 | ✅ Zstd | 实时压缩 + 高解压速度,RocksDB / MySQL 默认选择 |
Web 静态资源 | ✅ Brotli | 极致压缩比(JS/CSS/HTML),浏览器原生支持 |
API 动态数据 | ⚖️ Zstd/Gzip | Zstd(性能优先)或 Gzip(兼容性优先) |
老旧系统/嵌入式设备 | ✅ Gzip | 兼容性好,低内存占用,无需额外依赖 |
游戏资源打包 | ✅ Zstd | 比 ZIP 体积小 20%,加载更快 |
实时日志流处理 | ✅ Zstd | --adapt 参数动态调整级别,多线程支持 |
总结选择建议
- 追求性能:选 Zstd(数据库/日志/游戏)
- 追求压缩比:选 Brotli(Web 静态资源)
- 追求兼容性:选 Gzip(老旧系统/通用场景)
三者并非互斥,现代系统常 组合使用:
- 用 Brotli 压缩 Web 资源
- 用 Zstd 处理数据库/日志
- 用 Gzip 作为兼容性兜底
实现方案
Webpack 实现预压缩
Webpack 主要通过 compression-webpack-plugin
来实现与压缩文件的生成。
一般来说,我们会同时开启 gzip 和 brotli 压缩,优先使用 brotli 压缩,同时使用 gzip 作为兼容性兜底。
这是因为 brotli 压缩比更高,可以获得更好的压缩效果,而 gzip 兼容性更好,可以适配老旧版本的浏览器。
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [
// Gzip 压缩
new CompressionPlugin({
filename: "[path][base].gz", // 输出文件名模式
algorithm: "gzip", // 使用gzip算法
test: /\.(js|css|html|json|svg|txt|eot|otf|ttf|woff|woff2)$/, // 压缩文件类型
threshold: 10240, // 只压缩大于10KB的文件
minRatio: 0.8, // 只有压缩率优于80%才会压缩
deleteOriginalAssets: false, // 是否删除原始文件
cache: true, // 启用缓存
compressionOptions: {
level: 9, // 最高压缩级别
},
}),
// Brotli 压缩(需 Node.js ≥ v11.7.0)
new CompressionPlugin({
filename: "[path][base].br",
algorithm: "brotliCompress", // 使用 Node.js 原生 Brotli
test: /\.(js|css|html|svg|json)$/,
threshold: 50 * 1024, // 只压缩 >50KB 的文件
minRatio: 0.8,
compressionOptions: {
level: 11, // Brotli 压缩级别 (1-11)
params: {
// 高级参数(可选)
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, // 文本模式
[zlib.constants.BROTLI_PARAM_QUALITY]: 11, // 等同 level
},
},
}),
],
};
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
filename | String/Function | [path][base].gz | 生成的压缩文件名模式 |
algorithm | String/Function | gzip | 压缩算法,支持 gzip /deflate /deflateRaw |
test | RegExp/Array | 匹配所有文件 | 指定需要压缩的文件类型 |
threshold | Number | 0 | 文件大小阈值(字节),大于此值才压缩 |
minRatio | Number | 0.8 | 压缩比阈值,小于此值才压缩 |
deleteOriginalAssets | Boolean | false | 是否删除原始文件 |
cache | Boolean/String | false | 启用缓存或指定缓存目录 |
compressionOptions | Object | {} | 传递给 zlib 的选项 |
Vite 实现预压缩
可以使用 vite-plugin-compression
插件实现。
import { defineConfig } from "vite";
import viteCompression from "vite-plugin-compression";
export default defineConfig({
plugins: [
viteCompression({
verbose: true, // 打印压缩日志
disable: false, // 启用压缩
threshold: 50 * 1024, // 仅对大于 50KB 的文件进行压缩
algorithm: "gzip", // 使用 gzip 压缩
ext: ".gz", // 生成的文件后缀
}),
viteCompression({
verbose: true,
disable: false,
threshold: 50 * 1024, // 仅对大于 50KB 的文件进行压缩
algorithm: "brotliCompress", // 使用 Brotli 压缩
ext: ".br", // 生成的文件后缀
}),
],
});
如果实现更加精细的控制,比如针对不同文件类型使用不同压缩策略。可以使用 rollup-plugin-gzip
和 rollup-plugin-brotli
。
import { defineConfig } from "vite";
import gzip from "rollup-plugin-gzip";
import brotli from "rollup-plugin-brotli";
export default defineConfig({
build: {
rollupOptions: {
plugins: [
// 其他插件...
// Gzip 压缩
gzip({
filter: /\.(js|css|html|json|svg)$/, // 压缩文件类型
threshold: 10240, // 只压缩 >10KB 的文件
additionalFiles: [], // 额外压缩的文件
gzipOptions: { level: 9 }, // Gzip 压缩级别 (1-9)
}),
// Brotli 压缩
brotli({
filter: /\.(js|css|html|json|svg)$/,
threshold: 10240,
additionalFiles: [],
brotliOptions: {
quality: 11, // Brotli 压缩级别 (1-11)
mode: 1, // 0=generic, 1=text, 2=font
},
}),
],
},
},
});
Nginx 服务器
Nginx 服务器配置需要配置的参数
# 优先匹配br
brotli off; # 禁用动态压缩
brotli_static on; # 优先使用预压缩文件(如果有)
# 优先匹配 gzip
gzip off; # 禁用动态压缩
gzip_static on; # 优先使用预压缩文件(如果有)
gunzip on; # 兼容不支持gzip的客户端
gzip_vary on; # 确保响应头包含 Vary: Accept-Encoding
Node.js 作为服务器
例如使用 Express 作为服务器,手动实现需要注意以下几点:
- 需要根据请求头里的
Accept-Encoding
字段,判断浏览器支持的压缩格式。 - 如果浏览器不支持压缩,中间件不做任何处理,直接返回源文件。
- 如果浏览器支持压缩文件,则根据请求头返回对应的压缩文件,优先返回
.br
,其实选择.gz
。 - 需要根据对应压缩文件的内容,设置
Content-Encoding
、Content-Type
响应头。告知浏览器以什么样的格式去读取。
const express = require("express");
const path = require("path");
const fs = require("fs");
const app = express();
const staticDir = path.join(__dirname, "dist");
// 中间件:检查并返回预压缩文件
app.use((req, res, next) => {
const acceptEncoding = req.headers["accept-encoding"] || "";
const filePath = path.join(staticDir, req.path);
// 检查原始文件是否存在
if (!fs.existsSync(filePath)) return next();
// 优先返回 Brotli
if (acceptEncoding.includes("br") && fs.existsSync(`${filePath}.br`)) {
req.url = `${req.url}.br`;
res.set("Content-Encoding", "br");
}
// 次优先返回 Gzip
else if (acceptEncoding.includes("gzip") && fs.existsSync(`${filePath}.gz`)) {
req.url = `${req.url}.gz`;
res.set("Content-Encoding", "gzip");
}
next();
});
// 静态文件服务
app.use(
express.static(staticDir, {
setHeaders: (res, filePath) => {
// 修正压缩文件的 Content-Type(移除 .gz/.br 后缀)
if (filePath.endsWith(".br") || filePath.endsWith(".gz")) {
const originalExt = path.extname(filePath.replace(/\.(br|gz)$/, ""));
res.set("Content-Type", getMimeType(originalExt));
}
},
})
);
// 获取 MIME 类型
function getMimeType(ext) {
const types = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
".json": "application/json",
};
return types[ext] || "text/plain";
}
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
或者使用中间件 express-static-gzip
实现。
const express = require("express");
const expressStaticGzip = require("express-static-gzip");
const app = express();
// 自动处理 .gz 和 .br 文件
app.use(
"/",
expressStaticGzip("dist", {
enableBrotli: true, // 启用 Brotli
orderPreference: ["br"], // 优先返回 Brotli
serveStatic: {
maxAge: "1y", // 缓存一年
cacheControl: true,
setHeaders: (res, path) => {
// 可自定义响应头
},
},
})
);
app.listen(3000);
验证 Gzip 是否生效
浏览器开发者工具检查
- 打开 Chrome 开发者工具(F12)
- 切换到 Network 标签
- 刷新页面
- 点击任意资源文件,查看响应头:
- 应有
content-encoding: gzip
- 比较
Content-Length
和资源实际大小
- 应有
性能优化建议
静态文件:
- 使用 Webpack 预生成.gz 文件
- 配置 Nginx 优先使用预压缩文件(
gzip_static on
)
动态内容:
- 使用 Node.js 中间件实时压缩
- 设置适当的压缩级别(通常 6 是最佳平衡点)
缓存配置:
location ~* \.(js|css|html|json)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Vary "Accept-Encoding"; }
监控与调优:
- 监控服务器 CPU 使用率
- 根据实际流量调整
gzip_comp_level
- 定期检查压缩率是否正常
更新日志
e7112
-1于