#!/usr/bin/env node /** * 图标表注入:把生成好的 core 图标表写进 11 个端实现的标记块里。 * * 为什么要有这一步(而不是让 11 个端各自抄一份表): * 11 份手抄的表必然漂移 —— 今天给 core.js 加一个图标,明天有个端忘了同步, * 表现是"同一个 name 在 React 能渲染、在 Vue3 是空白",而且不会报错。 * 所以数据只有一份真源(core.js),各端只留**标记块**,内容由本脚本填。 * `tools/verify-icons.mjs` 会逐端比对标记块与 core.js,漂移当场拦下。 * * 标记格式(各端按自己的注释语法): * JS/CSS: /* === KOLE_ICON_TABLE:BEGIN === *​/ ... /* === KOLE_ICON_TABLE:END === *​/ * HTML: ... * * 注入后第 4 行是各自的分隔符写法差异 —— 见 FILES 表。 * * 用法: * node tools/inject-icon-table.mjs # 注入 * node tools/inject-icon-table.mjs --check # 只校验不写(CI 用) */ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const ICON_DIR = join(ROOT, '.design_library', 'kole-ui', 'icons'); const CORE_JS = join(ICON_DIR, 'core.js'); const REGISTRY = join(ICON_DIR, 'registry.json'); const ALIASES = join(ICON_DIR, 'aliases.json'); const CHECK = process.argv.includes('--check'); /** 需要注入的端文件(相对仓库根)。html 用 HTML 注释,其余用块注释。 */ const FILES = [ ['frameworks/Icon.html', 'html'], ['frameworks/Icon.jsx', 'js'], ['frameworks/Icon.vue2.vue', 'js'], ['frameworks/Icon.vue3.vue', 'js'], ['frameworks-mobile/Icon.html', 'html'], ['frameworks-mobile/Icon.jsx', 'js'], ['frameworks-mobile/Icon.vue2.vue', 'js'], ['frameworks-mobile/Icon.vue3.vue', 'js'], ['frameworks-mobile/Icon.uniapp.vue', 'js'], ]; const BEGIN = '=== KOLE_ICON_TABLE:BEGIN ==='; const END = '=== KOLE_ICON_TABLE:END ==='; function markers(kind) { return kind === 'html' ? [``, ``] : [`/* ${BEGIN} */`, `/* ${END} */`]; } function die(msg) { console.error('FAIL: ' + msg); process.exit(1); } function atomicWrite(file, content) { mkdirSync(dirname(file), { recursive: true }); const tmp = `${file}.tmp-${process.pid}`; writeFileSync(tmp, content.replace(/\r\n/g, '\n'), 'utf8'); renameSync(tmp, file); } if (!existsSync(CORE_JS)) die(`缺 ${CORE_JS} —— 先跑 node tools/build-icons.mjs`); /* 从 core.js 里取 CORE 字面量:它是 UMD 文件,require 一次最稳(不猜正则) */ const { createRequire } = await import('node:module'); const require2 = createRequire(import.meta.url); const runtime = require2(CORE_JS); const CORE = runtime.core; const coreNames = Object.keys(CORE).sort(); if (!coreNames.length) die('core.js 里没有图标'); /* 别名表:各端运行时要用它把历史名映射到 canonical 名 */ const aliasMap = JSON.parse(readFileSync(ALIASES, 'utf8')).aliases; /* 生成注入块。 * * 关键设计:**别名在这里就折叠掉**,注入的是「任何名字 → 路径」的终态表。 * 这样 11 个端只需要「查表 + 拼 SVG」两行逻辑,不需要各自实现别名解析 —— * 别名规则只有一处(build-icon-aliases.mjs),端上不可能漂移。 * * 体积取舍:compact 单行 JSON。实测 core 151 个约 33KB/文件,9 个文件共约 300KB。 * 换来的是每个端文件**自包含**(拷走单个文件就能用,dist 无需额外依赖)。 * 仓库既有 4 个移动端端实现已经是「各自内联一份 9 名字形表」的写法, * 这里是同一模式的规模化,不是新引入的坏味道。门禁逐端比对,漂移无法静默。 */ function buildBlock(kind) { const merged = {}; /* 先放 canonical,再放别名(别名不覆盖 canonical —— 别名表已保证不遮蔽真图标) */ for (const [name, paths] of Object.entries(CORE)) merged[name] = paths; for (const [alias, target] of Object.entries(aliasMap)) { if (!(alias in merged) && merged[target]) merged[alias] = merged[target]; } const payload = { sw: runtime.strokeWidth, vb: runtime.viewBox, i: merged, }; /* 让 `` : `/* 生成物:tools/inject-icon-table.mjs 注入(勿手改)。数据真源 .design_library/kole-ui/icons/。\n ${count} 个名字(${Object.keys(CORE).length} canonical + 别名折叠)· 描边 ${runtime.strokeWidth} · 别名规则见 build-icon-aliases.mjs */`; if (kind === 'html') { return `${head}\n`; } return `${head}\nconst KOLE_ICON_DATA = ${json};`; } /* 注入 */ let changed = 0; const missing = []; for (const [rel, kind] of FILES) { const abs = join(ROOT, rel); if (!existsSync(abs)) { missing.push(rel); continue; } const text = readFileSync(abs, 'utf8'); const [b, e] = markers(kind); const bi = text.indexOf(b); const ei = text.indexOf(e); if (bi < 0 || ei < 0 || ei < bi) { missing.push(`${rel}(缺标记对)`); continue; } const block = buildBlock(kind); const next = text.slice(0, bi + b.length) + '\n' + block + '\n' + text.slice(ei); if (next !== text) { changed++; if (!CHECK) atomicWrite(abs, next); } } if (missing.length) { console.log(` 跳过 ${missing.length} 个文件(未就绪或缺标记):`); missing.forEach((m) => console.log(' - ' + m)); } console.log('图标表注入'); console.log(` core 图标 ${coreNames.length} 个 | 别名 ${Object.keys(aliasMap).length} 条`); console.log(` ${CHECK ? '需更新' : '已更新'} ${changed} 个端文件 / 共 ${FILES.length} 个`); if (CHECK && changed) { die(`有 ${changed} 个端文件的图标表与 core.js 不一致 —— 跑 npm run build:icons 重新注入`); } console.log('OK');