配了三年 pako,今天发现浏览器把 gzip 彻底原生化了——这件事把前端压缩的账全变了
还在 import pako 做 gzip 压缩?这件事,今天被浏览器自己彻底收了。
前端的 IndexedDB 存大 JSON,API 响应 gzip 压缩上传,或者给 CDN 预压缩静态资源——这些场景在 2022 年之前,你基本没有选择:要么引入 pako(~45KB 纯 JS zlib 移植),要么自己手写原生实现,产出质量还参差不齐。
2022 年之后,这件事变了。
Chrome 80 / Safari 16.4 / Firefox 113 先后稳定支持了 CompressionStream API——浏览器把原生 zlib 引擎直接暴露给了 JavaScript。三个格式,gzip / deflate / deflate-raw,和你服务端协商的压缩格式完全兼容。
把 pako 换成原生,产出是一样的。
// Before — pako
import pako from "pako";
const compressed = pako.gzip(jsonString); // Uint8Array
// After — CompressionStream,原生
const compressed = await compress(jsonString); // Uint8Array,完全一样
只要把 pako.gzip() 替换成 compress(),产出二进制完全一致。CDN 不需要改配置,API 不需要改接收端,Wire Format 没变。
实战一:IndexedDB 存大 JSON,省 70% 体积
200KB 的 JSON 对象,用 gzip 压缩后通常是 15-30KB。对于移动端 IndexedDB 存储配额受限的场景,这直接决定了你的数据能不能存进去:
async function putCompressed(store, key, data) {
const compressed = await compress(JSON.stringify(data));
return store.put(compressed, key);
}
async function getCompressed(store, key) {
const compressed = await store.get(key);
if (!compressed) return null;
return JSON.parse(await decompress(compressed));
}
实战二:Web Worker 异步压缩,主线程零打扰
压缩是 CPU 密集操作,大文件在主线程跑会卡渲染。CompressionStream 支持 Web Worker,把压缩任务打发到后台:
// worker.js
self.onmessage = async ({ data }) => {
const compressed = await compress(JSON.stringify(data.payload));
self.postMessage({ compressed }, [compressed.buffer]);
};
主线程完全不阻塞,数据通过 Transferable 对象零拷贝传回。
pako 还能用吗?能。但理由变了。
压缩这件事,pako 是纯 JavaScript 实现,CompressionStream 调用的是浏览器底层 C 代码的 zlib——同一个引擎处理 HTTP 响应解压缩的那个。
Benchmark 数据(pako 官方仓库,Node v24,1MB 输入样本):
gzip-pako:~13 ops/secgzip-node zlib(即 CompressionStream 同款引擎):~30+ ops/sec
大文件(100KB+)场景下,浏览器原生实现比纯 JS 快 3-10 倍,因为没有 JS 执行开销,还能用 SIMD 指令。小文件(<10KB)差别不大,可以忽略。
另外,pako.min.gz ~15KB,打包进来就是白交 15KB 税。CompressionStream 体积是 0。
| 对比项 | CompressionStream(原生) | pako(第三方库) |
|---|---|---|
| 体积 | 0 依赖,0 打包体积 | ~45KB,~15KB gzipped |
| 性能 | 浏览器原生 C 引擎,3-10x JS | 纯 JS,v8 解释执行 |
| 格式兼容 | gzip/deflate,Wire Format 一致 | gzip/deflate,产出相同 |
| Worker 支持 | 原生支持,零拷贝 Transferable | 可用但有通信开销 |
| 浏览器支持 | Baseline 2022+,全主流引擎 | IE 都能跑 |
下一步:现在就能迁
不需要全量重写。把 pako 调用点列出来,然后:
// 封装一次,到处使用
async function compress(input, format = "gzip") {
const stream = new Blob([input]).stream()
.pipeThrough(new CompressionStream(format));
return new Uint8Array(await new Response(stream).arrayBuffer());
}
async function decompress(input, format = "gzip") {
const stream = new Blob([input]).stream()
.pipeThrough(new DecompressionStream(format));
return new Response(stream).text();
}
新代码直接用。旧代码里判断一下兼容性:if ("CompressionStream" in window),不兼容的才用 pako 做兜底。绝大多数 2022 年后的现代项目,走到第一个分支就够了。
一句话:之前靠 pako 续命的场景,现在浏览器自己会了,而且跑得更快、打包更小。
评论区
登录后可评论。