// 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_.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); });