Files
longli-mp/scripts/verify-build.mjs
2026-09-16 16:52:50 +08:00

89 lines
3.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 产物结构校验:把 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;
}