portfolio-site/zip.mjs(61 行)+ package.mjs(26 行)zlib 直接写出合法的 ZIP 文件;再用"文件清单 + sha256"保证打包内容可校验。
而且不想为了打个 zip 引入几十 MB 的依赖。
ZIP 的本质是"本地文件头 + 压缩数据 + 中央目录 + 结束记录",ZipWriter 把这四部分按规范拼出来(第 17-60 行,节选):
export class ZipWriter {
constructor(entries) { ... }
async build() {
const local = []; const central = []; let offset = 0;
for (const entry of this.entries) {
const name = Buffer.from(entry.name, "utf8");
const compressed = deflateRawSync(entry.content); // Node 内置压缩
const sum = checksum(entry.content); // CRC32 校验和
const header = Buffer.alloc(30);
header.writeUInt32LE(0x04034b50, 0); // 本地文件头签名
header.writeUInt16LE(0x0800, 6); // UTF-8 文件名
header.writeUInt16LE(8, 8); // 压缩方式 = deflate
header.writeUInt32LE(sum, 14);
...
const record = Buffer.concat([header, name, compressed]);
local.push(record);
// ...再写中央目录项(含 offset 指回本地头)
offset += record.length;
}
// ...最后写"中央目录结束记录"
}
}
CRC32 兼容性处理(第 1-15 行)——很实用的细节:
// Node 的 zlib.crc32 在较新版本才可用,旧版用上面的表兜底,保证行为一致。
function checksum(buffer) {
if (typeof crc32 === "function") return crc32(buffer) >>> 0;
let value = 0xffffffff;
for (const byte of buffer) value = CRC_TABLE[(value ^ byte) & 0xff] ^ (value >>> 8);
return (value ^ 0xffffffff) >>> 0;
}
配套的 package.mjs:按 build-manifest.json 列的清单收集文件、
逐个核对 sha256、再交给 ZipWriter 打包。
node:zlib,交付工具不该拖累项目依赖crc32 有就用、没有就自己算,保证"行为一致"package.mjs 有 sha256 校验逻辑;verify.mjs 会核对 manifestzipwriter-js(npm):一个文件、有类型声明、README 写清支持范围zipfile 打开生成的 zip"的跨语言验证脚本(增强可信度)archiver、jszip、adm-zip:功能全、社区大;但体积和依赖也大zlib 只做压缩,不管 ZIP 容器格式——所以还是得自己拼头第 19 课(零依赖构建与交付验收)