feat(3dtank): 本地镜像与 hornet 工具链迁到新版 bundle main.6469b966.js

旧 main.b7114b43.js 已被新构建替换(10,011,039 字节 / 1335 模块),本地镜像整体重切到新版并清掉旧产物;hornet 工具链按 tools/map_symbols.js 生成的新旧符号映射改名后重新打包(3dtank_bundle.js 与 unpacked/hornet_bundle.js 同步)。tools/symbol_map.json(8.9MB 映射产物)与切割解包目录不进库,可用 map_symbols.js / split.js 重新生成。
This commit is contained in:
TermiNexus committed 2026-09-17 19:59:40 +08:00
1 parent fd32881c3d
commit 90c377ad79
103 files changed
+400753 -399528

No files matched your search

+120 -25
View File
@@ -1,24 +1,49 @@
// split.js - 将 main.b7114b43.js 切割为多文件加载结构
// split.js - 将 main.<hash>.js 切割为多文件加载结构
// 优化: 资源模块(标准 t.exports=i.p+"..." 格式)用 JSON + 模板替代,不生成独立文件
// 用法: node split.js [bundle路径] [输出目录] [--clean] [--no-hornet]
// 未指定 bundle 时自动取项目根目录下最新的 main.<hash>.js
// 未指定输出目录时默认 3dtank.com/play/static/js
// --clean 先删除旧产物(unpacked/module_*.js、resources.json、旧 main.*.js;保留 hornet_*.js)
// --no-hornet 不在主文件末尾注入 hornet_bundle 自动加载
// 输出:
// ./3dtank.com/play/static/js/main.b7114b43.js (主文件: 加载器 + runtime)
// ./3dtank.com/play/static/js/unpacked/resources.json (资源模块 ID→路径映射)
// ./3dtank.com/play/static/js/unpacked/module_<id>.js (仅代码模块,约 126 个)
// <输出目录>/main.<hash>.js (主文件: 加载器 + runtime)
// <输出目录>/unpacked/resources.json (资源模块 ID→路径映射)
// <输出目录>/unpacked/module_<id>.js (仅代码模块)
const fs = require('fs');
const path = require('path');
const acorn = require('acorn');
const walk = require('acorn-walk');
const prettier = require('prettier');
const BUNDLE_PATH = path.resolve(__dirname, '..', 'main.b7114b43.js');
const OUT_DIR = path.resolve(__dirname, '..', '3dtank.com', 'play', 'static', 'js');
const ROOT = path.resolve(__dirname, '..');
// 自动取最新的 main.<hash>.js(排除输出目录里的加载器)
function AutoDetectBundle() {
const cands = fs.readdirSync(ROOT)
.filter((f) => /^main\.[0-9a-f]+\.js$/.test(f))
.map((f) => path.join(ROOT, f))
.filter((f) => fs.statSync(f).isFile())
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
return cands[0] || null;
}
const argv = process.argv.slice(2);
const flags = argv.filter((a) => a.startsWith('--'));
const positional = argv.filter((a) => !a.startsWith('--'));
const CLEAN = flags.indexOf('--clean') >= 0;
const WITH_HORNET = flags.indexOf('--no-hornet') < 0;
const LOADER_ONLY = flags.indexOf('--loader-only') >= 0; // 只重生成主文件,复用已有模块文件
const autoBundle = positional[0] ? null : AutoDetectBundle();
const BUNDLE_PATH = path.resolve(positional[0] || autoBundle || '');
const OUT_DIR = path.resolve(positional[1] || path.join(ROOT, '3dtank.com', 'play', 'static', 'js'));
const UNPACKED_DIR = path.join(OUT_DIR, 'unpacked');
const MAIN_PATH = path.join(OUT_DIR, 'main.b7114b43.js');
const MAIN_PATH = BUNDLE_PATH ? path.join(OUT_DIR, path.basename(BUNDLE_PATH)) : '';
const RESOURCES_JSON_PATH = path.join(UNPACKED_DIR, 'resources.json');
// UMD 包装模板
const UMD_PREFIX = `var __script_base_url__=(typeof document!=="undefined"&&document.currentScript&&document.currentScript.src)?document.currentScript.src.replace(/[^\\/]+$/,""):"./";!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.Launcher=n():t.Launcher=n()}(globalThis,async()=>{`;
const UMD_SUFFIX = `})//# sourceMappingURL=main.b7114b43.js.map\n`;
const UMD_SUFFIX = (bundlePath) => `})//# sourceMappingURL=${path.basename(bundlePath)}.map\n`;
// 资源模块识别:标准格式 (t,n,i)=>{"use strict";t.exports=i.p+"path"}
// 提取 path
@@ -39,7 +64,32 @@ function tryExtractResource(valueSrc) {
}
(async () => {
if (!BUNDLE_PATH || !fs.existsSync(BUNDLE_PATH)) {
console.error('找不到待切割的 bundle:' + (BUNDLE_PATH || '(未自动探测到 main.<hash>.js)'));
process.exit(1);
}
if (!fs.existsSync(UNPACKED_DIR)) fs.mkdirSync(UNPACKED_DIR, { recursive: true });
if (CLEAN) {
console.log('[0/7] 清理旧产物...');
let removed = 0;
for (const dir of [UNPACKED_DIR, OUT_DIR]) {
if (!fs.existsSync(dir)) continue;
for (const f of fs.readdirSync(dir)) {
const isModule = /^module_\d+\.js$/.test(f);
const isResources = f === 'resources.json';
const isOldMain = /^main\.[0-9a-f]+\.js$/.test(f) && path.join(dir, f) !== MAIN_PATH;
if (isModule || isResources || isOldMain) {
fs.unlinkSync(path.join(dir, f));
removed++;
}
}
}
console.log(` 已删除 ${removed} 个旧文件(保留 hornet_*.js)`);
}
console.log('[1/7] 读取原 bundle...');
console.log(` 文件: ${BUNDLE_PATH}`);
const source = fs.readFileSync(BUNDLE_PATH, 'utf8');
console.log(` 大小: ${(source.length / 1024 / 1024).toFixed(2)} MB`);
@@ -107,6 +157,12 @@ function tryExtractResource(valueSrc) {
const paddedId = typeof keyNum === 'number' ? String(keyNum).padStart(5, '0') : keyRaw;
const idLiteral = typeof keyNum === 'number' ? keyNum : JSON.stringify(keyRaw);
if (LOADER_ONLY && fs.existsSync(path.join(UNPACKED_DIR, `module_${paddedId}.js`))) {
codeModuleIds.push(keyNum);
codeCount++;
continue;
}
let beautified = valueSrc;
try {
beautified = await prettier.format('(' + valueSrc + ')', {
@@ -143,20 +199,24 @@ function tryExtractResource(valueSrc) {
console.log(` 完成。资源模块: ${resourceCount}, 代码模块: ${codeCount}, 美化失败: ${failCount}`);
console.log('[4/7] 写入 resources.json...');
fs.writeFileSync(
RESOURCES_JSON_PATH,
JSON.stringify(
{
type: 'webpack-resource-map',
generatedAt: new Date().toISOString(),
totalResources: resourceCount,
resourceMap,
},
null,
0
),
'utf8'
);
if (LOADER_ONLY && fs.existsSync(RESOURCES_JSON_PATH)) {
console.log(' --loader-only:复用已有 resources.json');
} else {
fs.writeFileSync(
RESOURCES_JSON_PATH,
JSON.stringify(
{
type: 'webpack-resource-map',
generatedAt: new Date().toISOString(),
totalResources: resourceCount,
resourceMap,
},
null,
0
),
'utf8'
);
}
console.log(` resources.json 大小: ${(fs.statSync(RESOURCES_JSON_PATH).size / 1024).toFixed(1)} KB`);
console.log('[5/7] 提取原 webpack runtime...');
@@ -170,7 +230,7 @@ function tryExtractResource(valueSrc) {
}
console.log(` runtime 代码长度: ${runtimeCode.length} 字符`);
console.log('[6/7] 生成主文件 main.b7114b43.js...');
console.log(`[6/7] 生成主文件 ${path.basename(BUNDLE_PATH)}...`);
const codeIdsArrayLiteral = codeModuleIds
.map((id) => (typeof id === 'number' ? id : JSON.stringify(id)))
.join(',');
@@ -211,14 +271,49 @@ await Promise.all(__code_ids__.map(async (__id__) => {
var __webpack_modules__ = window.__modules__;
`;
// 6.3 hornet 工具链自动加载(注入在 runtime 入口模块加载之后、return 之前)
const hornetCode = WITH_HORNET
? `
// ===== 暴露 webpack require(hornet 工具链 hook_gamemode/hornet_lib 依赖)=====
window.__wp_require__ = __webpack_require__;
// ===== 加载 hornet_bundle(默认关闭:URL 加 ?hornet=1 才加载,避免日志遮挡)=====
if (/[?&]hornet=1(&|$)/.test(location.search)) {
try {
const __hornet_url__ = __base_url__ + 'unpacked/hornet_bundle.js';
const __hornet_resp__ = await fetch(__hornet_url__);
if (__hornet_resp__.ok) {
const __hornet_code__ = await __hornet_resp__.text();
(0, eval)(__hornet_code__);
console.log('[Hornet] bundle 已加载');
} else {
console.warn('[Hornet] bundle 加载失败: HTTP ' + __hornet_resp__.status);
}
} catch (__hornet_err__) {
console.error('[Hornet] bundle 加载异常:', __hornet_err__);
}
}
`
: '';
let runtimeWithHornet = runtimeCode;
if (WITH_HORNET) {
const retIdx = runtimeWithHornet.lastIndexOf('return __webpack_exports__');
if (retIdx >= 0) {
runtimeWithHornet = runtimeWithHornet.slice(0, retIdx) + hornetCode + runtimeWithHornet.slice(retIdx);
} else {
console.warn(' [WARN] 未找到 return __webpack_exports__,跳过 hornet 注入');
}
}
const newBundle =
UMD_PREFIX +
'\n' +
loaderCode +
'\n// ===== 原 webpack runtime (一字未改) =====\n' +
runtimeCode +
runtimeWithHornet +
';\n' +
UMD_SUFFIX;
UMD_SUFFIX(BUNDLE_PATH);
fs.writeFileSync(MAIN_PATH, newBundle, 'utf8');
console.log(` 主文件大小: ${(newBundle.length / 1024).toFixed(1)} KB`);