Compress
@vafast/compress 根据客户端请求头 Accept-Encoding,在响应返回前压缩响应体,从而减小传输体积。支持 Brotli(br)/ gzip / deflate。
导出名是 compression(不是 compress):
import { compression } from '@vafast/compress'
// 也可用 default:import compression from '@vafast/compress'先搞清几个概念(给新用户)
响应压缩是做什么的?
浏览器或 HTTP 客户端在请求里声明「我能解压哪些格式」,服务端若同意,就把响应体压小后再发送,并在响应头里写上实际用了哪种算法。常见效果:JSON / HTML / 文本体积明显变小,带宽与加载时间下降。
Accept-Encoding 协商(本包如何选算法)
- 客户端例如发送:
Accept-Encoding: br, gzip, deflate - 中间件读取该头,按 逗号+空格(
,)拆成列表 - 用你配置的
encodings(默认['br', 'gzip', 'deflate'])去过滤:只保留同时出现在客户端列表里的项 - 过滤后的列表保留
encodings的顺序,取第一个作为最终算法
因此:服务端数组顺序 = 优先级。默认优先 Brotli,其次 gzip,再次 deflate。
注意(与完整 HTTP 协商的差异,以源码为准):
- 匹配是「字符串是否在拆分后的列表中」,不会解析
q权重(如gzip;q=0.8) - 若客户端写成
br,gzip(逗号后无空格),拆分结果可能对不上,导致不压缩 - 没有
Accept-Encoding,或与encodings无交集 → 不压缩
threshold(阈值)是什么?
对已经读成一整块缓冲区的响应:若 byteLength < threshold(默认 1024 字节),跳过压缩。
原因:很小的响应压完可能差不多大,还浪费 CPU。流式路径(见下)不走这个字节数判断。
zlib level / Brotli quality 白话
| 选项 | 作用 | 默认(本包) |
|---|---|---|
zlibOptions.level | gzip / deflate 的压缩级别,大致 0–9:越高越省体积、越费 CPU | 6 |
brotliOptions.params[BROTLI_PARAM_QUALITY] | Brotli 质量,大致 0–11:越高越省体积、越费 CPU | Node 的 BROTLI_DEFAULT_QUALITY(通常为 11) |
调优经验:API JSON 常用默认或略降 quality;CPU 紧张时可把 Brotli quality 降到 4–6,或干脆只用 gzip。
compressStream:类型注释 vs 运行时默认
| 来源 | 默认值 |
|---|---|
types.ts 的 JSDoc(@default false) | 写的是 false |
运行时 options?.compressStream ?? true | true |
以运行时为准:默认会压缩 ReadableStream body。SSE(text/event-stream)等长连接建议显式设 compressStream: false。
安装
npm install @vafast/compress快速开始
import { Server, defineRoute, defineRoutes, json, serve } from 'vafast'
import { compression } from '@vafast/compress'
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/',
handler: () => json({ message: 'Hello '.repeat(200) }),
}),
])
const server = new Server(routes)
server.use(compression())
serve({ fetch: server.fetch, port: 3000 })用支持压缩的客户端访问时,响应会带上 Content-Encoding(如 br)以及 Vary: accept-encoding。
用法
基础用法
推荐全局挂载:中间件先 await next(),再按需改写 body / 响应头。
server.use(compression())客户端需带可协商编码,例如:
Accept-Encoding: br, gzip, deflate常见场景
1. 只启用 gzip / deflate,并提高阈值
server.use(
compression({
encodings: ['gzip', 'deflate'],
threshold: 2048,
}),
)2. SSE / 流式响应:关闭流压缩
server.use(
compression({
compressStream: false,
}),
)说明:当 compressStream === true 且 response.body instanceof ReadableStream 时,走 pipeThrough(CompressionStream(...)),不检查 threshold / Content-Type 正则。SSE 请关掉流压缩。
3. 客户端跳过压缩
默认 disableByHeader: true:请求带任意值的 x-no-compression 头时直接透传。
GET /large-json
x-no-compression: 14. 调优压缩级别与内存缓存
非流式路径会对「算法 + 原文」做 MD5 键的进程内缓存(TTL 秒,默认 24 小时)。
import { constants } from 'node:zlib'
import { compression } from '@vafast/compress'
server.use(
compression({
zlibOptions: { level: 6 },
brotliOptions: {
params: {
[constants.BROTLI_PARAM_QUALITY]: 4,
},
},
TTL: 3600,
}),
)API
导出
| 导出 | 说明 |
|---|---|
compression | 中间件工厂(主入口) |
default | 同 compression |
CompressionStream | 把 Node zlib Transform 桥成 Web Streams,供流式 pipeThrough |
CompressionOptions / CompressionEncoding / LifeCycleOptions / CacheOptions 等 | 类型 |
选项 / 参数
compression(options?: CompressionOptions & LifeCycleOptions & CacheOptions)| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
encodings | ('br' | 'gzip' | 'deflate')[] | ['br', 'gzip', 'deflate'] | 服务端优先级;与 Accept-Encoding 求交后取第一个 |
threshold | number | 1024 | 缓冲响应小于该字节数则不压缩 |
disableByHeader | boolean | true | 为 true 时,请求含 x-no-compression 则跳过 |
compressStream | boolean | 运行时 true(类型 JSDoc 仍写 false,以运行为准) | 是否对 ReadableStream body 做流式压缩 |
brotliOptions | BrotliOptions | 缓冲路径:GENERIC + 默认 quality;流式路径默认 MODE_TEXT + 默认 quality | 传给 brotliCompressSync / createBrotliCompress |
zlibOptions | ZlibOptions | { level: 6 } | 传给 gzip / deflate |
TTL | number | 86400(24h) | 压缩结果内存缓存 TTL(秒);仅非流式 getOrCompress |
as | 'before' | 'after' | 'after' | 类型保留字段;当前实现读取后未使用,可忽略 |
相关方法
CompressionStream(encoding, options?)
供流式路径内部 pipeThrough 使用。一般业务代码不必直接调用。
最佳实践
- API JSON / HTML 适合全局开压缩;已经是
.zip/ 图片等二进制时,常因 Content-Type 不被匹配而自动跳过 - SSE / 长连接流式响应建议
compressStream: false - 用
threshold避免小响应白耗 CPU TTL缓存适合热点、内容重复的响应;注意进程内存占用,多实例各自一份缓存- 调试体积时可让客户端带
x-no-compression对比原文
注意事项
- 仅压缩
response.ok(状态码 200–299)的响应 - 无交集的
Accept-Encoding、或缺失该头 → 不压缩 - 缓冲路径下,
Content-Type需匹配可压缩类型(大致:非 event-stream 的text/*、json、xml、octet-stream等);无 Content-Type 时按可压缩处理 text/event-stream不在默认可压缩 Content-Type 正则内,但若compressStream: true且 body 是ReadableStream,仍可能走流压缩——SSE 请关compressStream- 会设置
Content-Encoding,并合并Vary: accept-encoding(已有Vary: *则不追加) - 依赖 Node
zlib/crypto(非纯 Edge 环境请自行确认兼容性)