/** * 微信小程序主包体积分析 * 用法:先执行发行构建(微信小程序),再运行:node scripts/analyze-mp-main.js * 输出:主包总大小、超过 2MB 时告警、主包内大文件列表 */ const fs = require('fs'); const path = require('path'); const BUILD_DIR = path.join(__dirname, '../unpackage/dist/build/mp-weixin'); const MAIN_PACKAGE_LIMIT_KB = 2048; function getSubPackageRoots() { const appJsonPath = path.join(BUILD_DIR, 'app.json'); if (!fs.existsSync(appJsonPath)) { console.error('未找到 app.json,请先执行:发行 -> 微信小程序'); process.exit(1); } const app = JSON.parse(fs.readFileSync(appJsonPath, 'utf8')); let roots = (app.subPackages || []).map((p) => p.root); // 若构建产物尚未包含最新分包,则按源码 pages.json 补充(如 goods_cate) const pagesJsonPath = path.join(__dirname, '../pages.json'); if (fs.existsSync(pagesJsonPath)) { let raw = fs.readFileSync(pagesJsonPath, 'utf8'); // 只去掉行尾 // 注释(不删字符串内的 //) raw = raw.replace(/\s*\/\/[^\n"']*$/gm, ''); try { const pagesJson = JSON.parse(raw); const subRoots = (pagesJson.subPackages || []).map((p) => p.root); subRoots.forEach((r) => { if (!roots.includes(r)) roots = roots.concat(r); }); } catch (_) {} } return roots; } function isInMainPackage(filePath, subRoots) { const normalized = filePath.replace(BUILD_DIR + path.sep, '').replace(/\\/g, '/'); for (const root of subRoots) { if (normalized === root || normalized.startsWith(root + '/')) return false; } return true; } function walkDir(dir, subRoots, list) { if (!fs.existsSync(dir)) return; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const e of entries) { const full = path.join(dir, e.name); const rel = path.relative(BUILD_DIR, full).replace(/\\/g, '/'); if (e.isDirectory()) { walkDir(full, subRoots, list); } else { if (!isInMainPackage(full, subRoots)) continue; try { const stat = fs.statSync(full); list.push({ path: rel, size: stat.size }); } catch (_) {} } } } function main() { const subRoots = getSubPackageRoots(); const files = []; walkDir(BUILD_DIR, subRoots, files); const totalBytes = files.reduce((s, f) => s + f.size, 0); const totalKB = Math.round(totalBytes / 1024); const totalMB = (totalBytes / 1024 / 1024).toFixed(2); console.log('========== 微信小程序主包体积分析 ==========\n'); console.log('主包总大小:', totalKB, 'KB', '(' + totalMB + ' MB)'); console.log('微信主包上限:', MAIN_PACKAGE_LIMIT_KB, 'KB (2 MB)\n'); if (totalKB > MAIN_PACKAGE_LIMIT_KB) { console.log('⚠️ 主包超出限制', totalKB - MAIN_PACKAGE_LIMIT_KB, 'KB,需优化后再上传。\n'); } else { console.log('✓ 主包未超限。\n'); } const sorted = files.slice().sort((a, b) => b.size - a.size); console.log('主包内大文件(按体积排序,前 30):'); console.log('----------------------------------------'); sorted.slice(0, 30).forEach((f) => { const kb = (f.size / 1024).toFixed(1); console.log(kb.padStart(8) + ' KB ' + f.path); }); console.log('\n主包页面(来自 app.json):'); const app = JSON.parse(fs.readFileSync(path.join(BUILD_DIR, 'app.json'), 'utf8')); (app.pages || []).forEach((p) => console.log(' -', p)); } main();