// test_loader.js - 模拟浏览器加载流程,验证切割版 bundle 能正确加载所有模块 // 新版: 资源模块从 resources.json 加载,代码模块从独立文件加载 const fs = require('fs'); const path = require('path'); const http = require('http'); const ROOT = path.resolve(__dirname, '..', '3dtank.com'); const JS_DIR = path.join(ROOT, 'play', 'static', 'js'); // 自动定位切割版主文件 main..js const MAIN_PATH = path.join(JS_DIR, fs.readdirSync(JS_DIR).filter((f) => /^main\.[0-9a-f]+\.js$/.test(f)).sort().pop() || ''); const UNPACKED_DIR = path.join(JS_DIR, 'unpacked'); const RESOURCES_JSON_PATH = path.join(UNPACKED_DIR, 'resources.json'); // 简单的 HTTP 服务器,提供静态文件 const server = http.createServer((req, res) => { let filePath = path.join(ROOT, req.url.split('?')[0]); if (req.url === '/' || req.url === '') filePath = path.join(ROOT, 'index.html'); if (!fs.existsSync(filePath)) { res.writeHead(404); res.end('Not Found'); return; } const ext = path.extname(filePath).toLowerCase(); const ct = { '.js': 'application/javascript', '.html': 'text/html', '.json': 'application/json', }[ext] || 'application/octet-stream'; res.writeHead(200, { 'Content-Type': ct }); fs.createReadStream(filePath).pipe(res); }); // 模拟 fetch function makeFetch() { return (url) => new Promise((resolve, reject) => { http .get(url, (res) => { let body = ''; res.on('data', (d) => (body += d)); res.on('end', () => { resolve({ ok: res.statusCode === 200, status: res.statusCode, text: () => Promise.resolve(body), json: () => Promise.resolve(JSON.parse(body)), }); }); }) .on('error', reject); }); } (async () => { // 1. 检查文件结构 console.log('[1/5] 检查文件结构...'); const codeFiles = fs.readdirSync(UNPACKED_DIR).filter((f) => /^module_\d+\.js$/.test(f)); console.log(` unpacked/ 中代码模块文件: ${codeFiles.length}`); if (!fs.existsSync(RESOURCES_JSON_PATH)) { console.error(' ✗ resources.json 不存在'); process.exit(1); } const resourcesJson = JSON.parse(fs.readFileSync(RESOURCES_JSON_PATH, 'utf8')); const resourceMap = resourcesJson.resourceMap; const resourceCount = Object.keys(resourceMap).length; console.log(` resources.json 资源模块: ${resourceCount}`); console.log(` 合计: ${codeFiles.length + resourceCount}`); // 2. 启动 HTTP 服务器 console.log('[2/5] 启动测试 HTTP 服务器...'); await new Promise((resolve) => server.listen(18888, resolve)); console.log(' 服务器监听 http://localhost:18888/'); const baseUrl = 'http://localhost:18888/play/static/js/'; const fetch = makeFetch(); // 创建模拟的 window 对象 const sandbox = { console: console, fetch, globalThis: null, }; sandbox.window = sandbox; sandbox.globalThis = sandbox; sandbox.window.__modules__ = {}; // 3. 阶段 1: 加载 resources.json,注册资源模块 console.log('[3/5] 阶段 1: 从 resources.json 注册资源模块...'); for (const __id__ in resourceMap) { if (!Object.prototype.hasOwnProperty.call(resourceMap, __id__)) continue; const __path__ = resourceMap[__id__]; const __id_num__ = Number(__id__); sandbox.window.__modules__[__id_num__] = (function (__p__) { return (t, n, i) => { 'use strict'; t.exports = i.p + __p__; }; })(__path__); } console.log(` 已注册资源模块: ${Object.keys(sandbox.window.__modules__).length}`); // 4. 阶段 2: 加载代码模块(fetch + eval) console.log('[4/5] 阶段 2: 从独立文件加载代码模块 (fetch + eval)...'); const mainCode = fs.readFileSync(MAIN_PATH, 'utf8'); // 提取 __code_ids__ 数组 const m = mainCode.match(/const __code_ids__ = \[([^\]]+)\]/); if (!m) { console.error(' ✗ 主文件中找不到 __code_ids__'); process.exit(1); } const codeIds = m[1].split(',').map((x) => parseInt(x.trim(), 10)); console.log(` 待加载代码模块: ${codeIds.length}`); let loadedCount = 0; let failedCount = 0; const failedModules = []; const BATCH_SIZE = 50; for (let i = 0; i < codeIds.length; i += BATCH_SIZE) { const batch = codeIds.slice(i, i + BATCH_SIZE); await Promise.all( batch.map(async (id) => { const paddedId = String(id).padStart(5, '0'); const url = baseUrl + 'unpacked/module_' + paddedId + '.js'; try { const resp = await fetch(url); if (!resp.ok) throw new Error('HTTP ' + resp.status); const code = await resp.text(); // 在 sandbox 中 eval 执行 const fn = new Function('window', 'console', 'globalThis', code); fn(sandbox.window, sandbox.console, sandbox); loadedCount++; } catch (e) { failedCount++; failedModules.push({ id, error: e.message }); } }) ); if ((i / BATCH_SIZE) % 2 === 0) { process.stdout.write(` 进度: ${loadedCount}/${codeIds.length} (失败 ${failedCount})\r`); } } console.log(` 完成: ${loadedCount}/${codeIds.length} 加载成功, ${failedCount} 失败 `); if (failedCount > 0) { console.log(' 失败模块 (前 10):'); failedModules.slice(0, 10).forEach((f) => console.log(` ID ${f.id}: ${f.error}`)); } // 5. 验证 window.__modules__ 注册情况 console.log('[5/5] 验证 window.__modules__ 注册...'); const modules = sandbox.window.__modules__; if (!modules) { console.error(' ✗ window.__modules__ 未定义'); server.close(); process.exit(1); } const registeredIds = Object.keys(modules).map(Number); const totalExpected = resourceCount + codeIds.length; console.log(` 已注册模块总数: ${registeredIds.length} (预期 ${totalExpected})`); // 关键入口模块验证 const keyIds = [17, 76074, 13431, 26482, 78231, 82347, 87072]; keyIds.forEach((id) => { const exists = typeof modules[id] === 'function'; const isResource = Object.prototype.hasOwnProperty.call(resourceMap, id); const tag = isResource ? '资源' : '代码'; console.log(` 模块 ${id} [${tag}]: ${exists ? '✓' : '✗'}`); }); // 检查资源模块是否正确返回路径 let resourceOkCount = 0; let resourceFailCount = 0; for (const id in resourceMap) { const idNum = Number(id); const expectedPath = resourceMap[id]; const mod = modules[idNum]; if (typeof mod !== 'function') { resourceFailCount++; continue; } // 模拟调用: i.p = "/play/" const fakeModule = { exports: {} }; const fakeRequire = { p: '/play/' }; try { mod(fakeModule, {}, fakeRequire); const actualPath = fakeModule.exports; if (actualPath === '/play/' + expectedPath) { resourceOkCount++; } else { resourceFailCount++; if (resourceFailCount <= 3) { console.log(` 路径不匹配 ID ${id}: 期望 "/play/${expectedPath}", 实际 "${actualPath}"`); } } } catch (e) { resourceFailCount++; } } console.log(` 资源模块路径验证: ${resourceOkCount}/${resourceCount} 正确`); // 检查代码模块是否都注册为函数 const codeMissing = codeIds.filter((id) => typeof modules[id] !== 'function'); console.log(` 代码模块全部注册: ${codeMissing.length === 0 ? '✓' : '✗ 缺失 ' + codeMissing.length + ' 个'}`); if (codeMissing.length > 0) { console.log(` 缺失 ID (前 10): ${codeMissing.slice(0, 10).join(', ')}`); } server.close(); console.log('\n=== 测试完成 ==='); const allOk = failedCount === 0 && registeredIds.length === totalExpected && resourceOkCount === resourceCount && codeMissing.length === 0; if (allOk) { console.log('✓ 所有模块加载并注册成功 (资源从 JSON + 代码从文件)'); } else { console.log('✗ 存在加载或注册失败'); process.exit(1); } })().catch((err) => { console.error('Fatal:', err); server.close(); process.exit(1); });