251 lines
9.1 KiB
JavaScript
251 lines
9.1 KiB
JavaScript
// split.js - 将 main.b7114b43.js 切割为多文件加载结构
|
|
// 优化: 资源模块(标准 t.exports=i.p+"..." 格式)用 JSON + 模板替代,不生成独立文件
|
|
// 输出:
|
|
// ./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 个)
|
|
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 UNPACKED_DIR = path.join(OUT_DIR, 'unpacked');
|
|
const MAIN_PATH = path.join(OUT_DIR, 'main.b7114b43.js');
|
|
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`;
|
|
|
|
// 资源模块识别:标准格式 (t,n,i)=>{"use strict";t.exports=i.p+"path"}
|
|
// 提取 path
|
|
function tryExtractResource(valueSrc) {
|
|
// 标准格式: t.exports = i.p + "..."
|
|
const m = valueSrc.match(
|
|
/\{\s*"use strict";\s*(?:t|n|i)\.exports\s*=\s*(?:t|n|i)\.p\+"([^"]+)"\s*;?\s*\}/
|
|
);
|
|
if (m) return { type: 'std', path: m[1] };
|
|
|
|
// 变体: t.exports = i.p + "..." (无 use strict 或无大括号)
|
|
const m2 = valueSrc.match(
|
|
/(?:t|n|i)\.exports\s*=\s*(?:t|n|i)\.p\+"([^"]+)"\s*;?/
|
|
);
|
|
if (m2 && valueSrc.length < 200) return { type: 'std', path: m2[1] };
|
|
|
|
return null;
|
|
}
|
|
|
|
(async () => {
|
|
console.log('[1/7] 读取原 bundle...');
|
|
const source = fs.readFileSync(BUNDLE_PATH, 'utf8');
|
|
console.log(` 大小: ${(source.length / 1024 / 1024).toFixed(2)} MB`);
|
|
|
|
console.log('[2/7] 解析 AST...');
|
|
const ast = acorn.parse(source, {
|
|
ecmaVersion: 'latest',
|
|
sourceType: 'script',
|
|
allowReturnOutsideFunction: true,
|
|
});
|
|
|
|
let modulesDecl = null;
|
|
walk.simple(ast, {
|
|
VariableDeclarator(node) {
|
|
if (node.id && node.id.name === '__webpack_modules__' && node.init && node.init.type === 'ObjectExpression') {
|
|
modulesDecl = node;
|
|
}
|
|
},
|
|
});
|
|
if (!modulesDecl) throw new Error('未找到 __webpack_modules__');
|
|
|
|
const objStart = modulesDecl.init.start;
|
|
const objEnd = modulesDecl.init.end;
|
|
|
|
console.log('[3/7] 分类提取模块 (资源模块 vs 代码模块)...');
|
|
const props = modulesDecl.init.properties;
|
|
console.log(` 共 ${props.length} 个模块`);
|
|
|
|
const resourceMap = {}; // id -> path (资源模块)
|
|
const codeModuleIds = []; // 代码模块 ID 列表
|
|
let idx = 0;
|
|
let failCount = 0;
|
|
let resourceCount = 0;
|
|
let codeCount = 0;
|
|
|
|
for (const prop of props) {
|
|
idx++;
|
|
// 计算 ID
|
|
let keyRaw;
|
|
let keyNum;
|
|
if (prop.key.type === 'Identifier') {
|
|
keyRaw = prop.key.name;
|
|
keyNum = Number(keyRaw);
|
|
if (isNaN(keyNum)) keyNum = keyRaw;
|
|
} else if (prop.key.type === 'Literal') {
|
|
keyRaw = String(prop.key.value);
|
|
keyNum = typeof prop.key.value === 'number' ? prop.key.value : keyRaw;
|
|
} else {
|
|
keyRaw = `unknown_${idx}`;
|
|
keyNum = keyRaw;
|
|
}
|
|
|
|
const valueSrc = source.slice(prop.value.start, prop.value.end);
|
|
|
|
// 尝试识别为资源模块
|
|
const resource = tryExtractResource(valueSrc);
|
|
if (resource) {
|
|
// 资源模块:存入 resourceMap,不生成独立文件
|
|
const idKey = typeof keyNum === 'number' ? keyNum : keyRaw;
|
|
resourceMap[idKey] = resource.path;
|
|
resourceCount++;
|
|
continue;
|
|
}
|
|
|
|
// 代码模块:美化并写入独立文件
|
|
const paddedId = typeof keyNum === 'number' ? String(keyNum).padStart(5, '0') : keyRaw;
|
|
const idLiteral = typeof keyNum === 'number' ? keyNum : JSON.stringify(keyRaw);
|
|
|
|
let beautified = valueSrc;
|
|
try {
|
|
beautified = await prettier.format('(' + valueSrc + ')', {
|
|
parser: 'babel',
|
|
semi: true,
|
|
singleQuote: false,
|
|
printWidth: 110,
|
|
tabWidth: 2,
|
|
});
|
|
beautified = beautified.trim();
|
|
if (beautified.startsWith('(') && beautified.endsWith(')')) {
|
|
beautified = beautified.slice(1, -1).trim();
|
|
}
|
|
while (beautified.endsWith(';')) beautified = beautified.slice(0, -1).trimEnd();
|
|
} catch (e) {
|
|
failCount++;
|
|
}
|
|
|
|
const filePath = path.join(UNPACKED_DIR, `module_${paddedId}.js`);
|
|
const fileContent =
|
|
`// ============================================================\n` +
|
|
`// Module ID: ${keyRaw}\n` +
|
|
`// ============================================================\n` +
|
|
`;(window.__modules__ = window.__modules__ || {})[${idLiteral}] = ${beautified};\n`;
|
|
fs.writeFileSync(filePath, fileContent, 'utf8');
|
|
|
|
codeModuleIds.push(keyNum);
|
|
codeCount++;
|
|
|
|
if (idx % 200 === 0) {
|
|
console.log(` 已处理 ${idx}/${props.length} (资源 ${resourceCount}, 代码 ${codeCount})`);
|
|
}
|
|
}
|
|
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'
|
|
);
|
|
console.log(` resources.json 大小: ${(fs.statSync(RESOURCES_JSON_PATH).size / 1024).toFixed(1)} KB`);
|
|
|
|
console.log('[5/7] 提取原 webpack runtime...');
|
|
const runtimeStart = objEnd;
|
|
const returnIdx = source.lastIndexOf('return __webpack_exports__');
|
|
if (returnIdx === -1) throw new Error('找不到 return __webpack_exports__');
|
|
const returnEnd = returnIdx + 'return __webpack_exports__'.length;
|
|
let runtimeCode = source.slice(runtimeStart, returnEnd);
|
|
if (runtimeCode.startsWith(',')) {
|
|
runtimeCode = ';var ' + runtimeCode.slice(1);
|
|
}
|
|
console.log(` runtime 代码长度: ${runtimeCode.length} 字符`);
|
|
|
|
console.log('[6/7] 生成主文件 main.b7114b43.js...');
|
|
const codeIdsArrayLiteral = codeModuleIds
|
|
.map((id) => (typeof id === 'number' ? id : JSON.stringify(id)))
|
|
.join(',');
|
|
|
|
const loaderCode = `
|
|
// ===== 切割版模块加载器 =====
|
|
window.__modules__ = {};
|
|
const __base_url__ = window.__script_base_url__ || './';
|
|
|
|
// 6.1 加载资源模块 (从 resources.json 批量注册)
|
|
const __resource_resp__ = await fetch(__base_url__ + 'unpacked/resources.json');
|
|
if (!__resource_resp__.ok) throw new Error('resources.json load failed: HTTP ' + __resource_resp__.status);
|
|
const __resource_data__ = await __resource_resp__.json();
|
|
const __resource_map__ = __resource_data__.resourceMap;
|
|
for (const __id__ in __resource_map__) {
|
|
if (!Object.prototype.hasOwnProperty.call(__resource_map__, __id__)) continue;
|
|
const __path__ = __resource_map__[__id__];
|
|
const __id_num__ = Number(__id__);
|
|
window.__modules__[__id_num__] = (function(__p__) {
|
|
return (t, n, i) => {
|
|
"use strict";
|
|
t.exports = i.p + __p__;
|
|
};
|
|
})(__path__);
|
|
}
|
|
|
|
// 6.2 加载代码模块 (fetch + eval 独立文件)
|
|
const __code_ids__ = [${codeIdsArrayLiteral}];
|
|
await Promise.all(__code_ids__.map(async (__id__) => {
|
|
const __padded__ = typeof __id__ === 'number' ? String(__id__).padStart(5, '0') : __id__;
|
|
const __url__ = __base_url__ + 'unpacked/module_' + __padded__ + '.js';
|
|
const __resp__ = await fetch(__url__);
|
|
if (!__resp__.ok) throw new Error('Module ' + __id__ + ' load failed: HTTP ' + __resp__.status + ' @ ' + __url__);
|
|
const __code__ = await __resp__.text();
|
|
(0, eval)(__code__);
|
|
}));
|
|
|
|
var __webpack_modules__ = window.__modules__;
|
|
`;
|
|
|
|
const newBundle =
|
|
UMD_PREFIX +
|
|
'\n' +
|
|
loaderCode +
|
|
'\n// ===== 原 webpack runtime (一字未改) =====\n' +
|
|
runtimeCode +
|
|
';\n' +
|
|
UMD_SUFFIX;
|
|
|
|
fs.writeFileSync(MAIN_PATH, newBundle, 'utf8');
|
|
console.log(` 主文件大小: ${(newBundle.length / 1024).toFixed(1)} KB`);
|
|
|
|
console.log('[7/7] 验证语法...');
|
|
try {
|
|
acorn.parse(newBundle, {
|
|
ecmaVersion: 'latest',
|
|
sourceType: 'script',
|
|
allowReturnOutsideFunction: true,
|
|
});
|
|
console.log(' ✓ 主文件语法正确');
|
|
} catch (e) {
|
|
console.error(` ✗ 语法错误: ${e.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('\n=== 完成 ===');
|
|
console.log(` 总模块数: ${props.length}`);
|
|
console.log(` 资源模块: ${resourceCount} (存入 resources.json)`);
|
|
console.log(` 代码模块: ${codeCount} (独立文件)`);
|
|
console.log(` 美化失败: ${failCount}`);
|
|
console.log(` 主文件: ${MAIN_PATH}`);
|
|
console.log(` 资源 JSON: ${RESOURCES_JSON_PATH}`);
|
|
console.log(` 代码模块目录: ${UNPACKED_DIR}\\`);
|
|
})().catch((err) => {
|
|
console.error('Fatal:', err);
|
|
process.exit(1);
|
|
});
|