执行 ROADMAP 的 S1-P4,按规划的三阶段走,本次完成 A 与 B。 阶段 A · 预计算 scenarios(验证「构建期预计算 + 运行时回退」链路) - tools/precompute.mjs:与 app.js 的 extractScenarios 逻辑完全一致, 构建期算出结果写进 data.components[].scenarios - app.js 加快速路径:优先读 c.scenarios,缺失时回退运行时解析 - 浏览器实测:预计算值与运行时提取值**完全一致**(逐字节比对) - 实测收益有限:仅 4 个组件有 ≥2 场景(场景卡片本就少见), 但链路验证通过,为阶段 C 的 API 预计算铺路 阶段 B · css 源码分离(零风险,css 不参与任何解析) - css 从 data.js 移到 site/sources/<slug>/css.txt(79 个文件) - data.js 里改为 sourcesRef.css 引用,data.json 保持完整(For Agents 承诺) - data.js: 1058 KB → 942 KB(−116 KB) - app.js 新增 srcOf(c, kind) / hasSrc(c, kind): · srcOf 同步返回已有源码,缺失时触发后台 fetch 并缓存 · __aaSrcReady 回调在加载完成后填充代码区 - 修正回调时机 bug:原实现捕获构建时的 current(初始 tab), 导致用户后续切换的 tab 收不到通知。改为回调内实时比对当前 tab 关于 P4 的实测修正(已登记 ROADMAP 规划修正记录) - 规划估计「拆 sources → ~110KB」。实测 css 仅占 119KB/888KB, 且 html/vue2/vue3/jsx 共 765KB 被**渲染期同步消费** (extractComponentAPI 读 vue3/vue2/jsx,extractScenarios 读 html) - 故需分阶段:A+B 先减 116KB,阶段 C 需先预计算 API 表才能分离其余 4 端 新增构建链(两步,不可省第二步) - npm run build:site = build-site.ps1 + precompute.mjs - build-site.ps1 会把 data.js 重写回全量,precompute 才做瘦身 - 已写入 AGENTS.md 的铁律章节(含「只跑第一步会怎样」的后果说明) 验证 - 回归 100%:1003/1003,79 页全通过,N/A 35 - 浏览器实测:展开代码区 → CSS tab → 2121 字符正常显示, Network 出现 sources/button/css.txt 请求 - 预计算 vs 运行时场景提取结果逐字节一致
99 lines
3.8 KiB
JavaScript
99 lines
3.8 KiB
JavaScript
// P4:`data.js` 瘦身 —— 分步降低运行时对源码全文的依赖
|
||
//
|
||
// 背景(实测):
|
||
// data.json 991KB,其中 components[].sources 占 888KB(89.6%)
|
||
// 各端源码量:html 245KB / vue2 184KB / vue3 181KB / jsx 155KB / css 119KB
|
||
//
|
||
// 哪些 sources 被【渲染期同步】消费(改造难点):
|
||
// extractComponentAPI → vue3 / vue2 / jsx(3 端)
|
||
// extractScenarios → html(1 端)
|
||
// css → 不参与任何解析(可安全分离)
|
||
//
|
||
// 策略:
|
||
// 阶段 A(本文件)—— 预计算 scenarios,验证「构建期预计算 + 运行时回退」链路可行
|
||
// 阶段 B(后续)—— 分离 css 到独立文件(零风险,不参与解析)
|
||
// 阶段 C(后续)—— 预计算 API 表,再分离 4 端源码
|
||
//
|
||
// 设计原则:运行时保留回退路径,预计算缺失时功能不受影响。
|
||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
||
|
||
const ROOT = 'C:/Users/12914/Desktop/组件规范第一套';
|
||
const DATA_JSON = `${ROOT}/site/data.json`;
|
||
const DATA_JS = `${ROOT}/site/data.js`;
|
||
const SRC_DIR = `${ROOT}/site/sources`;
|
||
|
||
if (!existsSync(DATA_JSON)) {
|
||
console.error('[FATAL] 找不到 site/data.json,请先运行 build-site.ps1');
|
||
process.exit(1);
|
||
}
|
||
|
||
const data = JSON.parse(readFileSync(DATA_JSON, 'utf8'));
|
||
|
||
/* 与 app.js 的 extractScenarios 完全一致(保证结果可复现) */
|
||
function extractScenarios(c) {
|
||
const html = (c.sources && c.sources.html) || '';
|
||
const bodyM = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
||
const body = bodyM ? bodyM[1] : html;
|
||
const out = [];
|
||
const re = /<h2[^>]*>([\s\S]*?)<\/h2>/gi;
|
||
let m;
|
||
while ((m = re.exec(body)) !== null) {
|
||
let text = m[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
||
text = text.replace(/^\d+[.、]\s*/, '');
|
||
if (text) out.push(text);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/* ---------- 阶段 A:预计算 scenarios ---------- */
|
||
|
||
let withScenarios = 0;
|
||
let totalScenarios = 0;
|
||
for (const c of data.components) {
|
||
const s = extractScenarios(c);
|
||
c.scenarios = s;
|
||
totalScenarios += s.length;
|
||
if (s.length >= 2) withScenarios++; // app.js 的 buildScenarioCard 要求 >= 2
|
||
}
|
||
console.log(`[precompute] 阶段A · scenarios:${totalScenarios} 条 / ${withScenarios} 个组件有 ≥2 条`);
|
||
|
||
/* ---------- 阶段 B:分离 css 源码到独立文件 ----------
|
||
css 不参与任何解析(extractComponentAPI 只读 vue3/vue2/jsx,extractScenarios 只读 html),
|
||
因此可安全移出 data.js,改为按需 fetch。
|
||
data.json 保持完整(For Agents 承诺不变)。 */
|
||
|
||
if (!existsSync(SRC_DIR)) mkdirSync(SRC_DIR, { recursive: true });
|
||
|
||
let cssSplit = 0;
|
||
for (const c of data.components) {
|
||
if (!c.sources || !c.sources.css) continue;
|
||
const dir = `${SRC_DIR}/${c.slug}`;
|
||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||
writeFileSync(`${dir}/css.txt`, c.sources.css);
|
||
cssSplit++;
|
||
}
|
||
console.log(`[precompute] 阶段B · css 已分离:${cssSplit} 个文件 → site/sources/<slug>/css.txt`);
|
||
|
||
/* 在 data.js 里把 css 换成引用(data.json 保持完整) */
|
||
|
||
const dataForJs = JSON.parse(JSON.stringify(data));
|
||
for (const c of dataForJs.components) {
|
||
if (c.sources && c.sources.css) {
|
||
c.sourcesRef = c.sourcesRef || {};
|
||
c.sourcesRef.css = `sources/${c.slug}/css.txt`;
|
||
delete c.sources.css;
|
||
}
|
||
}
|
||
|
||
/* ---------- 写回 ---------- */
|
||
|
||
writeFileSync(DATA_JSON, JSON.stringify(data, null, 2) + '\n');
|
||
|
||
const js = '/* AUTO-GENERATED by build-site.ps1 + tools/precompute.mjs - do not edit by hand */\r\nwindow.AA_DATA = ' + JSON.stringify(dataForJs) + ';\r\n';
|
||
writeFileSync(DATA_JS, js);
|
||
|
||
const beforeKb = 1058;
|
||
const afterKb = (Buffer.byteLength(js) / 1024).toFixed(0);
|
||
console.log(`[precompute] data.js: ${beforeKb} KB → ${afterKb} KB(−${beforeKb - afterKb} KB)`);
|
||
|