// pack_unpacked.js — 把切割产物打成单个包,减少「一堆小文件」的请求开销 // // 打包对象:3dtank.com/play/static/js/unpacked/ 下的 // · module_*.js (126 个代码模块) // · resources.json (资源映射表) // 产物:3dtank.com/play/static/js/unpacked.pack.json // { version, base, files: { "<文件名>": "<内容>" } } // version = 文件名+内容的 sha1,用于 SW 缓存版本号(内容不变则版本不变) // // 谁在用: // 1) 加载器(main.*.js)优先读这个包,一次请求拿到全部模块(失败才回退逐文件 fetch) // 2) hornet_sw.js 用它把 /play/static/js/unpacked/* 的请求就地命中(不再走磁盘小文件) // // 用法:node pack_unpacked.js [unpacked目录] [输出文件] // 不带参数时默认打包 ../../../3dtank.com/play/static/js/unpacked const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const DEFAULT_DIR = path.resolve(__dirname, '..', '3dtank.com', 'play', 'static', 'js', 'unpacked'); const UNPACKED_DIR = process.argv[2] ? path.resolve(process.argv[2]) : DEFAULT_DIR; const OUT_FILE = process.argv[3] ? path.resolve(process.argv[3]) : path.join(path.dirname(UNPACKED_DIR), 'unpacked.pack.json'); function main() { if (!fs.existsSync(UNPACKED_DIR)) { console.error('[ERROR] 目录不存在: ' + UNPACKED_DIR); process.exit(1); } const Names = fs .readdirSync(UNPACKED_DIR) .filter((N) => /^module_\d+\.js$/.test(N) || N === 'resources.json') .sort(); if (Names.length === 0) { console.error('[ERROR] 没找到 module_*.js / resources.json(先跑 split.js 切割)'); process.exit(1); } const Hash = crypto.createHash('sha1'); const Files = {}; let Total = 0; for (const Name of Names) { const Text = fs.readFileSync(path.join(UNPACKED_DIR, Name), 'utf8'); Files[Name] = Text; Hash.update(Name).update('\u0000').update(Text); Total += Buffer.byteLength(Text, 'utf8'); } const Pack = { version: Hash.digest('hex').slice(0, 16), base: '/play/static/js/unpacked/', count: Names.length, files: Files, }; const Json = JSON.stringify(Pack); fs.writeFileSync(OUT_FILE, Json); console.log('[OK] 打包 ' + Names.length + ' 个文件 ' + (Total / 1048576).toFixed(2) + ' MB'); console.log(' 版本: ' + Pack.version); console.log(' 输出: ' + OUT_FILE + ' (' + (Json.length / 1048576).toFixed(2) + ' MB)'); } main();