refactor(css): CSS 第一阶段瘦身——止血 + 拆分 ecommerce-standalone(#6)

This commit is contained in:
2026-06-15 18:32:14 +08:00
parent 62fcf461b6
commit 643595bede
5 changed files with 14186 additions and 14084 deletions
+2 -1
View File
@@ -10,7 +10,8 @@
"type-check": "tsc -p tsconfig.json --noEmit", "type-check": "tsc -p tsconfig.json --noEmit",
"test": "vitest", "test": "vitest",
"test:run": "vitest run", "test:run": "vitest run",
"test:coverage": "vitest run --coverage" "test:coverage": "vitest run --coverage",
"css:audit": "node scripts/css-audit.mjs"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "5.3.0", "@ant-design/icons": "5.3.0",
+87
View File
@@ -0,0 +1,87 @@
// CSS 健康度审计脚本。
// 用法: npm run css:audit
// 输出每个 CSS 文件的行数、选择器数、!important 数、@media 数,
// 以及 !important 密度(每 100 行的 !important 数)。
// 用于建立基线、跟踪 CSS 瘦身进度、防止 !important 回潮。
import { readFileSync, readdirSync, statSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join, relative } from "node:path";
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "styles");
const REPORT = [];
function scanCssFile(filePath) {
const content = readFileSync(filePath, "utf-8");
const lines = content.split(/\r?\n/).length;
const selectors = (content.match(/\{/g) || []).length;
const important = (content.match(/!important/g) || []).length;
const media = (content.match(/@media/g) || []).length;
const density = lines > 0 ? ((important / lines) * 100).toFixed(1) : "0";
return { lines, selectors, important, media, density };
}
function walk(dir) {
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
const st = statSync(full);
if (st.isDirectory()) {
walk(full);
} else if (entry.endsWith(".css")) {
const rel = relative(ROOT, full).replace(/\\/g, "/");
REPORT.push({ file: rel, ...scanCssFile(full) });
}
}
}
walk(ROOT);
// Sort by !important count descending to surface the worst offenders.
REPORT.sort((a, b) => b.important - a.important);
const totals = REPORT.reduce(
(acc, r) => {
acc.lines += r.lines;
acc.selectors += r.selectors;
acc.important += r.important;
acc.media += r.media;
return acc;
},
{ lines: 0, selectors: 0, important: 0, media: 0 },
);
const pad = (s, n) => String(s).padEnd(n);
const num = (s, n) => String(s).padStart(n);
console.log("\nCSS Audit Report — src/styles/\n");
console.log(
`${pad("File", 52)} ${num("Lines", 7)} ${num("Sel", 6)} ${num("!imp", 7)} ${num("@media", 7)} imp/100ln`,
);
console.log("-".repeat(92));
for (const r of REPORT) {
console.log(
`${pad(r.file, 52)} ${num(r.lines, 7)} ${num(r.selectors, 6)} ${num(r.important, 7)} ${num(r.media, 7)} ${r.density}`,
);
}
console.log("-".repeat(92));
console.log(
`${pad("TOTAL", 52)} ${num(totals.lines, 7)} ${num(totals.selectors, 6)} ${num(totals.important, 7)} ${num(totals.media, 7)} ${((totals.important / totals.lines) * 100).toFixed(1)}`,
);
console.log("");
// Exit non-zero if total !important exceeds a budget threshold.
// Current baseline: ~7795. Set budget slightly above to allow incremental work
// while preventing uncontrolled growth.
const IMPORTANT_BUDGET = 7820;
if (totals.important > IMPORTANT_BUDGET) {
console.error(
`FAIL: !important count ${totals.important} exceeds budget ${IMPORTANT_BUDGET}. ` +
`Run with --no-important-check to bypass (not recommended).`,
);
process.exit(1);
} else {
console.log(
`OK: !important count ${totals.important} within budget ${IMPORTANT_BUDGET} ` +
`(headroom ${IMPORTANT_BUDGET - totals.important}).`,
);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff