// scripts/generate-tokens.ts // 从 src/styles/tokens.scss + variables.scss 解析 CSS 变量, // 输出 src/styles/token-values.ts 供 JS 运行时使用 import { readFileSync, writeFileSync } from 'fs'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); interface TokenMap { [key: string]: string; } /** 解析 SCSS 文件中的 CSS 变量声明 */ function parseTokensFromScss( scssContent: string, selector: string = 'page', ): TokenMap { const tokens: TokenMap = {}; const blockRegex = new RegExp( `${selector.replace('.', '\\.')}\\s*\\{([\\s\\S]+?)\\n\\}`, ); const blockMatch = scssContent.match(blockRegex); if (!blockMatch) return tokens; const varRegex = /--tk-([\w-]+)\s*:\s*([^;]+);/g; let match: RegExpExecArray | null; while ((match = varRegex.exec(blockMatch[1])) !== null) { tokens[match[1]] = match[2].trim(); } return tokens; } /** 解析 SCSS 变量(如 $pri: #C4623A)用于替换 #{...} 引用 */ function parseScssVariables(scssContent: string): Record { const vars: Record = {}; const regex = /\$([\w-]+)\s*:\s*([^;]+);/g; let match: RegExpExecArray | null; while ((match = regex.exec(scssContent)) !== null) { vars[match[1]] = match[2].trim(); } return vars; } /** 解析 tokens.scss,替换 SCSS 变量引用 */ function resolveTokenValues( tokensContent: string, variablesContent: string, selector: string, ): TokenMap { const scssVars = parseScssVariables(variablesContent); const rawTokens = parseTokensFromScss(tokensContent, selector); const resolved: TokenMap = {}; for (const [key, value] of Object.entries(rawTokens)) { let resolvedValue = value; resolvedValue = resolvedValue.replace( /#\{\$([\w-]+)\}/g, (_, varName) => scssVars[varName] || '', ); resolved[key] = resolvedValue; } return resolved; } function generateTokenFile( tokensPath: string, variablesPath: string, outputPath: string, ): void { const tokensContent = readFileSync(tokensPath, 'utf-8'); const variablesContent = readFileSync(variablesPath, 'utf-8'); const normalTokens = resolveTokenValues( tokensContent, variablesContent, 'page', ); const elderTokens = resolveTokenValues( tokensContent, variablesContent, '.elder-mode', ); const doctorTokens = resolveTokenValues( tokensContent, variablesContent, '.doctor-mode', ); const output = `// Auto-generated by scripts/generate-tokens.ts — DO NOT EDIT // Generated at: ${new Date().toISOString()} export const TOKEN_VALUES = ${JSON.stringify(normalTokens, null, 2)} as const; export const ELDER_TOKEN_OVERRIDES = ${JSON.stringify(elderTokens, null, 2)} as const; export const DOCTOR_TOKEN_OVERRIDES = ${JSON.stringify(doctorTokens, null, 2)} as const; // Canvas 专用:字号(数字,单位 px) export const CANVAS_FONT_NORMAL = { yLabel: 10, xLabel: 10, tooltip: 12, pointNormal: 3, pointAbnormal: 5, } as const; export const CANVAS_FONT_ELDER = { yLabel: 14, xLabel: 14, tooltip: 16, pointNormal: 5, pointAbnormal: 8, } as const; `; writeFileSync(outputPath, output, 'utf-8'); console.log(`[generate-tokens] Generated ${outputPath}`); console.log(` Normal: ${Object.keys(normalTokens).length} tokens`); console.log(` Elder: ${Object.keys(elderTokens).length} overrides`); console.log(` Doctor: ${Object.keys(doctorTokens).length} overrides`); } // CLI 入口 if (process.argv[1]?.endsWith('generate-tokens.ts')) { const root = resolve(__dirname, '..'); generateTokenFile( resolve(root, 'src/styles/tokens.scss'), resolve(root, 'src/styles/variables.scss'), resolve(root, 'src/styles/token-values.ts'), ); } export { parseTokensFromScss, parseScssVariables, resolveTokenValues, generateTokenFile };