为什么是 WARC 和 JSON Schema,而不是 protobuf
上一版这个项目用 Go + Bazel + protobuf 写的。这一版全部推翻了,理由是 2040 年。
这个项目 2020 年 5 月就开始了。 前一版是 its-my-data/doubak: Go 写的命令行工具,Bazel 构建,数据存 protobuf。它能用,也真的抓下了几百 MB 的档案。 技术上没什么问题。
推翻它的是一个问题:2040 年,一个跟这个项目毫无关系的人拿到这份档案, 他能读懂吗?
protobuf 的字节,离开 .proto 就是垃圾
protobuf 的编码是纯二进制,字段名不在数据里,只有字段编号。
没有对应版本的 .proto 文件,那就是一坨不透明的字节——
你甚至看不出里面有几个字段。而档案的生命周期比任何一个代码仓库都长。
NDJSON 不一样:一行一条记录,字段名就写在数据里,
十四年后拿 jq、拿 grep、拿任何一个文本编辑器都能读。
慢一点、大一点,但它不需要任何配套物就能被理解。对一个存档格式来说,
这个性质压倒性能。
所以规则是:从 schema 生成代码,绝不反过来。 schema 是规范文本的一部分,代码只是它的一个实现。
还有一个更直接的原因
扩展跑在 Manifest V3 上,而 MV3 的内容安全策略禁止 unsafe-eval。
protobuf.js 的动态模式正好需要它。也就是说,就算不谈 2040 年,
这条路在浏览器里本来也走不通。
WARC 保持原味
页面本身存成 WARC,一个 1996 年就开始用、2009 年成为 ISO 标准的格式, 互联网档案馆存了几千亿个页面用的就是它。关键决定是不往 WARC 里塞私货: 不自定义记录类型,不加豆备专有的头。
这样 ReplayWeb.page、pywb 这些现成工具
可以原封不动地打开一份豆备档案,不需要知道豆备是什么。
所有豆备特有的元数据——抓取意图、判定结果、覆盖率、连续性证明——
都放在旁边的 index.ndjson 里。
代价是档案里有冗余(WARC 里有一份,index 里有一份摘要), 换来的是任何一方单独存在都有意义: 只有 WARC,你还能重放;只有 index,你还知道抓了什么。
零依赖,同一个理由
扩展本身也是零运行时依赖、零开发依赖、零构建步骤——
用原生 ES 模块,类型写在 JSDoc 里,测试用 Node 自带的 node:test。
三个理由叠在一起:这个项目的整个论点就是可审计和长寿;
扩展会碰到用户的登录会话,每一个依赖都是供应链攻击面;
以及——那些看起来需要库的事情,其实浏览器原生都有。
gzip 是 CompressionStream,摘要是 crypto.subtle,
而一条 WARC 记录说穿了就是几行文本头加一段负载。
结果是:仓库里的源码就是浏览器里跑的东西。 想审计它的人不需要先信任一条构建流水线。