/** * 产物结构校验:把 dist 里所有 WXML 的 class 抽出来,回查是否都有对应 WXSS 规则。 * 用途:Tailwind 改造后确认「模板写的类」与「编译出的样式」没有错配(尤其任意值类会被 * weapp-tailwindcss 重写成安全类名,肉眼看不出来)。 * 用法:node scripts/verify-build.mjs [distDir] */ import fs from "node:fs"; import path from "node:path"; const ROOT = path.resolve(process.argv[2] || "dist/build/mp-weixin"); if (!fs.existsSync(ROOT)) { console.error(`找不到构建产物目录:${ROOT}\n请先执行 npm run build:mp-weixin`); process.exit(1); } const cssFiles = []; const wxmlFiles = []; (function walk(dir) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) walk(full); else if (entry.name.endsWith(".wxss")) cssFiles.push(full); else if (entry.name.endsWith(".wxml")) wxmlFiles.push(full); } })(ROOT); /* ---- 1. 收集 WXSS 中定义过的类名 ---- */ const defined = new Set(); let cssBytes = 0; for (const file of cssFiles) { const css = fs.readFileSync(file, "utf8"); cssBytes += Buffer.byteLength(css); // 选择器里的 .class,可能带反斜杠转义(如 .w-\[10px\]) for (const m of css.matchAll(/\.((?:[A-Za-z0-9_-]|\\.)+)/g)) { defined.add(m[1].replace(/\\/g, "")); } } /* ---- 2. 收集 WXML 里 class 属性用到的 token ---- */ const used = new Map(); let staticCount = 0; let dynamicCount = 0; for (const file of wxmlFiles) { const src = fs.readFileSync(file, "utf8"); for (const m of src.matchAll(/class="([^"]*)"/g)) { let value = m[1]; if (value.includes("{{")) { // 动态绑定:把三元/对象里的字符串字面量也捞出来一起校验 dynamicCount += 1; for (const lit of value.match(/'[^']*'|"[^"]*"/g) || []) value += ` ${lit.slice(1, -1)}`; value = value.replace(/\{\{[^}]*\}\}/g, " "); } else { staticCount += 1; } for (const token of value.split(/\s+/)) { const cls = token.trim(); if (!cls) continue; if (!used.has(cls)) used.set(cls, new Set()); used.get(cls).add(path.relative(ROOT, file)); } } } /* ---- 3. 校验 ---- */ const missing = []; for (const [cls, files] of used) { if (!defined.has(cls)) missing.push([cls, [...files].slice(0, 3)]); } console.log("产物目录 :", path.relative(process.cwd(), ROOT)); console.log("wxss 文件 :", cssFiles.length, `(合计 ${(cssBytes / 1024).toFixed(1)} KB)`); console.log("app.wxss :", `${(fs.statSync(path.join(ROOT, "app.wxss")).size / 1024).toFixed(1)} KB`); console.log("wxml 文件 :", wxmlFiles.length); console.log("class 静态处数 :", staticCount, " 动态处数 :", dynamicCount); console.log("涉及 class :", used.size); console.log(""); if (missing.length === 0) { console.log("✅ 结构校验通过:所有 class 均有对应样式规则,0 错配"); } else { console.log(`❌ 有 ${missing.length} 个 class 找不到样式规则:`); for (const [cls, files] of missing.slice(0, 80)) { console.log(` .${cls} <- ${files.join(", ")}`); } process.exitCode = 1; }