feat(miniprogram): Token 常量生成脚本 + useCanvasTokens hook (E2-1 Phase 2)
- 新增 scripts/generate-tokens.ts 从 SCSS 解析 CSS 变量生成 token-values.ts - 新增 useCanvasTokens hook 供 Canvas 组件适老化/医生端切换 - vitest include 扩展覆盖 scripts/__tests__/ - 10 单元测试覆盖 SCSS 解析和变量替换
This commit is contained in:
133
apps/miniprogram/scripts/generate-tokens.ts
Normal file
133
apps/miniprogram/scripts/generate-tokens.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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<string, string> {
|
||||
const vars: Record<string, string> = {};
|
||||
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 };
|
||||
Reference in New Issue
Block a user