Skip to content

Compress

@vafast/compress 根据客户端请求头 Accept-Encoding,在响应返回前压缩响应体,从而减小传输体积。支持 Brotli(br)/ gzip / deflate

导出名是 compression(不是 compress):

typescript
import { compression } from '@vafast/compress'
// 也可用 default:import compression from '@vafast/compress'

先搞清几个概念(给新用户)

响应压缩是做什么的?

浏览器或 HTTP 客户端在请求里声明「我能解压哪些格式」,服务端若同意,就把响应体压小后再发送,并在响应头里写上实际用了哪种算法。常见效果:JSON / HTML / 文本体积明显变小,带宽与加载时间下降。

Accept-Encoding 协商(本包如何选算法)

  1. 客户端例如发送:Accept-Encoding: br, gzip, deflate
  2. 中间件读取该头,按 逗号+空格, )拆成列表
  3. 用你配置的 encodings(默认 ['br', 'gzip', 'deflate'])去过滤:只保留同时出现在客户端列表里的项
  4. 过滤后的列表保留 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.levelgzip / deflate 的压缩级别,大致 0–9:越高越省体积、越费 CPU6
brotliOptions.params[BROTLI_PARAM_QUALITY]Brotli 质量,大致 0–11:越高越省体积、越费 CPUNode 的 BROTLI_DEFAULT_QUALITY(通常为 11)

调优经验:API JSON 常用默认或略降 quality;CPU 紧张时可把 Brotli quality 降到 4–6,或干脆只用 gzip。

compressStream:类型注释 vs 运行时默认

来源默认值
types.ts 的 JSDoc(@default false写的是 false
运行时 options?.compressStream ?? truetrue

运行时为准:默认会压缩 ReadableStream body。SSE(text/event-stream)等长连接建议显式设 compressStream: false

安装

bash
npm install @vafast/compress

快速开始

typescript
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 / 响应头。

typescript
server.use(compression())

客户端需带可协商编码,例如:

http
Accept-Encoding: br, gzip, deflate

常见场景

1. 只启用 gzip / deflate,并提高阈值

typescript
server.use(
  compression({
    encodings: ['gzip', 'deflate'],
    threshold: 2048,
  }),
)

2. SSE / 流式响应:关闭流压缩

typescript
server.use(
  compression({
    compressStream: false,
  }),
)

说明:当 compressStream === trueresponse.body instanceof ReadableStream 时,走 pipeThrough(CompressionStream(...))不检查 threshold / Content-Type 正则。SSE 请关掉流压缩。

3. 客户端跳过压缩

默认 disableByHeader: true:请求带任意值的 x-no-compression 头时直接透传。

http
GET /large-json
x-no-compression: 1

4. 调优压缩级别与内存缓存

非流式路径会对「算法 + 原文」做 MD5 键的进程内缓存TTL 秒,默认 24 小时)。

typescript
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中间件工厂(主入口)
defaultcompression
CompressionStream把 Node zlib Transform 桥成 Web Streams,供流式 pipeThrough
CompressionOptions / CompressionEncoding / LifeCycleOptions / CacheOptions类型

选项 / 参数

typescript
compression(options?: CompressionOptions & LifeCycleOptions & CacheOptions)
参数类型默认说明
encodings('br' | 'gzip' | 'deflate')[]['br', 'gzip', 'deflate']服务端优先级;与 Accept-Encoding 求交后取第一个
thresholdnumber1024缓冲响应小于该字节数则不压缩
disableByHeaderbooleantruetrue 时,请求含 x-no-compression 则跳过
compressStreamboolean运行时 true(类型 JSDoc 仍写 false,以运行为准)是否对 ReadableStream body 做流式压缩
brotliOptionsBrotliOptions缓冲路径:GENERIC + 默认 quality;流式路径默认 MODE_TEXT + 默认 quality传给 brotliCompressSync / createBrotliCompress
zlibOptionsZlibOptions{ level: 6 }传给 gzip / deflate
TTLnumber86400(24h)压缩结果内存缓存 TTL(秒);仅非流式 getOrCompress
as'before' | 'after''after'类型保留字段;当前实现读取后未使用,可忽略

相关方法

CompressionStream(encoding, options?)

供流式路径内部 pipeThrough 使用。一般业务代码不必直接调用。

最佳实践

  1. API JSON / HTML 适合全局开压缩;已经是 .zip / 图片等二进制时,常因 Content-Type 不被匹配而自动跳过
  2. SSE / 长连接流式响应建议 compressStream: false
  3. threshold 避免小响应白耗 CPU
  4. TTL 缓存适合热点、内容重复的响应;注意进程内存占用,多实例各自一份缓存
  5. 调试体积时可让客户端带 x-no-compression 对比原文

注意事项

  • 仅压缩 response.ok(状态码 200–299)的响应
  • 无交集的 Accept-Encoding、或缺失该头 → 不压缩
  • 缓冲路径下,Content-Type 需匹配可压缩类型(大致:非 event-stream 的 text/*jsonxmloctet-stream 等);无 Content-Type 时按可压缩处理
  • text/event-stream 不在默认可压缩 Content-Type 正则内,但若 compressStream: true 且 body 是 ReadableStream,仍可能走流压缩——SSE 请关 compressStream
  • 会设置 Content-Encoding,并合并 Vary: accept-encoding(已有 Vary: * 则不追加)
  • 依赖 Node zlib / crypto(非纯 Edge 环境请自行确认兼容性)

相关链接