Design Token 的跨端契约测试:基于 JSON Schema 的全平台类型合规检验
Design Token 的跨端契约测试:基于 JSON Schema 的全平台类型合规检验

在大型企业级跨端基础设施建设中,Design Token 仓库(tokens.json)是全公司设计与研发之间最底层的“视觉契约(Visual Contract)”。
一份简单的 JSON 文件,通过自动化流水线会被编译分发到 Web(CSS/SCSS)、iOS(Swift/SwiftUI)、Android(Kotlin/Jetpack Compose)以及 Flutter(Dart) 四大终端平台。
然而,在多团队协作与持续集成的现实中,设计令牌的源文件往往极其脆弱:
- 某位设计师在 Figma 里手滑导出了一个格式错误的 Hex 色号(如
#4F46E只有 5 位数,或者误将 RGB 写成了包含透明度的非标格式); - 某个层级的别名引用拼写错误(如
{color.brand.indigo.500}写成了{color.brand.indgo.500}),导致下游 iOS 编译时直接抛出死锁异常并阻断主工程发版; - 某个原本是纯数字的间距 Token(如
16),被无意修改为了带单位的字符串"16rem",导致 Android Compose 无法将其解析为Dp强类型常量而全面报错!
传统的测试手段(如跑单元测试或等待 App 编译失败)耗时极长且反馈滞后。
基于 JSON Schema 与 DTCG 规范的“跨端静态契约测试(Cross-Platform Contract Testing)”,是在 Token 代码合并入主干前,毫秒级拦截所有非法类型与拓扑漏洞的最强质量护城河。
跨端契约测试的核心四维校验矩阵
一份符合全平台工业级要求的 Design Token,必须在自动化门禁中通过以下四个维度的严格契约校验:
[源头 Token 提交: tokens/**/*.json]
│
▼ (运行静态契约测试流水线: test:tokens)
┌────────────────┴────────────────┐
├── 1. 结构与模式合规 (JSON Schema Validation)
│ - 必须包含 $value, $type, $description 规范字段
│ - 拦截非标字段与拼写错误
│
├── 2. 色彩与数值物理合法性 (Physical Validity)
│ - Hex 必须严格满足 3/6/8 位标准正规表达式
│ - 间距与尺寸必须为正数,杜绝非法负值与非标单位
│
├── 3. 别名引用拓扑闭包 (Topological Closure)
│ - 所有 {xxx.yyy} 别名必须能精准寻址到已存在的合法实体
│ - 深度遍历检测是否存在 A -> B -> A 的循环死锁引用!
│
└── 4. 跨端保留字与命名规范 (Identifier Safety)
- 拦截 C++ / Swift / Kotlin 中的语言保留字 (如 class, default, switch)
- 强制遵循 kebab-case 命名规范
│
▼
[✅ 100% 校验通过 ──> 放心分发全平台编译!]
生产级 JSON Schema 契约定义规范
我们编写一套基于 Draft 2020-12 的核心 Token Schema 约束文件(token-contract.schema.json):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "DesignSystemTokenContract",
"type": "object",
"patternProperties": {
"^[a-z0-9]+(-[a-z0-9]+)*$": {
"type": "object",
"properties": {
"$value": {
"type": ["string", "number", "object"]
},
"$type": {
"type": "string",
"enum": ["color", "dimension", "fontFamily", "fontWeight", "duration", "cubicBezier", "shadow"]
},
"$description": {
"type": "string",
"minLength": 5
}
},
"required": ["$value", "$type"],
"additionalProperties": true
}
}
}
编写端到端契约校验引擎(Ajv + 拓扑环路检测)
// scripts/token-contract-tester.ts
import * as fs from 'fs';
import * as path from 'path';
import Ajv from 'ajv/dist/2020';
import { globSync } from 'glob';
const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
export interface ContractTestResult {
passed: boolean;
errors: string[];
}
export class TokenContractTester {
private schemaValidator: any;
private tokenRegistry: Map<string, any> = new Map();
constructor(schemaPath: string) {
const rawSchema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
this.schemaValidator = ajv.compile(rawSchema);
}
// 1. 全量静态加载并打平 Token 路径
public loadTokens(globPattern: string) {
const files = globSync(globPattern);
for (const file of files) {
const content = JSON.parse(fs.readFileSync(file, 'utf8'));
this.flattenNode(content, '');
}
}
private flattenNode(node: any, prefix: string) {
for (const [key, val] of Object.entries(node)) {
const currentPath = prefix ? `${prefix}.${key}` : key;
if (val && typeof val === 'object' && '$value' in val) {
this.tokenRegistry.set(currentPath, val);
} else if (val && typeof val === 'object' && !Array.isArray(val)) {
this.flattenNode(val, currentPath);
}
}
}
// 2. 核心契约检验
public runAudit(): ContractTestResult {
const errors: string[] = [];
// A. 校验 Schema 基础结构
for (const [tokenPath, tokenObj] of this.tokenRegistry.entries()) {
const valid = this.schemaValidator(tokenObj);
if (!valid) {
this.schemaValidator.errors?.forEach((err: any) => {
errors.push(`[Schema 违规] Token \`${tokenPath}\` ${err.instancePath} ${err.message}`);
});
}
// B. 针对 Color 类型的严格正则与物理合法性
if (tokenObj.$type === 'color' && typeof tokenObj.$value === 'string' && !tokenObj.$value.startsWith('{')) {
const hexRegex = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
if (!hexRegex.test(tokenObj.$value)) {
errors.push(`[非法色值] Token \`${tokenPath}\` 包含了不合法的 Hex 格式: "${tokenObj.$value}"`);
}
}
}
// C. 别名拓扑闭包与死循环引用检测 (DFS 环路检测)
for (const tokenPath of this.tokenRegistry.keys()) {
const cycle = this.detectCircularDependency(tokenPath, new Set());
if (cycle) {
errors.push(`[别名死锁] 捕获循环依赖引用链: ${cycle.join(' -> ')}`);
}
}
return { passed: errors.length === 0, errors };
}
private detectCircularDependency(currentPath: string, visited: Set<string>): string[] | null {
if (visited.has(currentPath)) {
return [...Array.from(visited), currentPath];
}
const tokenObj = this.tokenRegistry.get(currentPath);
if (!tokenObj || typeof tokenObj.$value !== 'string') return null;
const aliasMatch = tokenObj.$value.match(/\{([^}]+)\}/);
if (!aliasMatch) return null;
const targetAlias = aliasMatch[1];
if (!this.tokenRegistry.has(targetAlias)) {
// 别名悬空
return null;
}
visited.add(currentPath);
const result = this.detectCircularDependency(targetAlias, new Set(visited));
visited.delete(currentPath);
return result;
}
}
CI 门禁集成与阻止非法合入
在 GitHub Actions 或 GitLab CI 中,将契约测试作为绝对不可绕过的必过项(Blocking Check):
# .github/workflows/token-contract-ci.yml
name: Design Token Contract Gate
on: [pull_request]
jobs:
validate-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Run Token Contract Tests
run: |
npm install
npx ts-node scripts/token-contract-tester.ts
总结
跨端设计系统的稳健性,取决于最底层的类型契约有多严密。通过构建基于 JSON Schema 的全自动静态契约测试套件,在代码提交阶段把非法色号、非标单位、悬空引用与死循环别名彻底扼杀在摇篮之中,我们才能让单份 Token 资产在流向 iOS、Android、Web 与 Flutter 全端时,成为每一个终端平台都可以绝对信赖的高品质编译基石。
更多推荐




所有评论(0)