@tabula-css/tokens
Tabula 的扁平 DTCG 配置档案解析器、值语义校验器、轴解析器与生成器。
安装
@tabula-css/tokens 不属于快速上手指南中需要直接安装的包——它是 @tabula-css/registry(以及借由它,@tabula-css/cli)的传递依赖,而大多数项目正是用后者把令牌转换为一次构建。只有当你在围绕令牌流水线本身构建自定义工具时——例如一个在不运行完整 tabula build 的情况下校验令牌文件或将其解析为 CSS 的脚本——才需要直接安装它:
npm install @tabula-css/tokens概览
在这里,一个设计令牌(design token)不再只是 JSON,而是变成系统其余部分可以信任的值。@tabula-css/tokens 实现了快速上手中描述的构建流水线第 1–5 步:解析令牌与配置文件,校验值语义(不仅是形状——一个 { value: 0, unit: "px" } 的 font-size 在 JSON 层面是合法的,但在这里会是一个构建错误),将轴映射与别名解析为字面量矩阵,并生成四个 W1 产物(theme.css、tokens.resolved.json、vocabulary.txt、types.d.ts)。它产出的已解析模型——合成出的词汇表、其预期的声明、自定义属性表——正是 @tabula-css/registry 用来从 Tailwind 真实编译输出中推导 registry.json 的输入。
导出
buildProfile()
export interface BuildResult {
readonly ok: boolean;
readonly diagnostics: readonly Diagnostic[];
readonly model?: ResolvedModel; // present only when validation passed
readonly artifacts?: Artifacts; // present only when validation passed
}
export function buildProfile(
tokensJson: string,
configJson: string,
options?: ValidateOptions,
): BuildResult;端到端运行整个 W1 切片——解析、校验、解析(resolve)、生成——并且失败即关闭:只要出现任何 error 级别的诊断,就不会返回 model 或 artifacts。如果你只想要“输入令牌 + 配置,输出产物或诊断”,这就是唯一需要调用的函数。
import { readFileSync } from 'node:fs';
import { buildProfile } from '@tabula-css/tokens';
const result = buildProfile(
readFileSync('tokens/base.tokens.json', 'utf8'),
readFileSync('tabula.config.json', 'utf8'),
);
if (!result.ok) {
for (const d of result.diagnostics) console.error(`${d.code}: ${d.message}`);
process.exit(1);
}
console.log(result.artifacts['vocabulary.txt']);parseTokens()
export interface ParseResult {
readonly ast: TokensFile | null;
readonly diagnostics: readonly Diagnostic[];
}
export function parseTokens(json: string): ParseResult;将令牌文件字符串解析为 AST。这一阶段只保证输入是 RFC 8259 JSON 且顶层为对象——不允许 JSON5、注释、尾随逗号或前导 BOM——否则返回 TAB-E101 且 ast: null。值语义由 validateTokens() 单独校验。
validateConfig() / parseConfig()
export interface ConfigResult {
readonly ok: boolean;
readonly config: TabulaConfig | null;
readonly diagnostics: readonly Diagnostic[];
}
export function validateConfig(input: unknown): ConfigResult;
export function parseConfig(json: string): ConfigResult;校验一个已解析的配置对象(validateConfig)或一段原始 JSON 字符串(parseConfig,它还会在 JSON 无效时报告 TAB-E101)。两者都在代码中以结构化方式校验 tabula.config.json——运行时不使用任何 JSON Schema 引擎,因此这个包唯一的依赖是 @tabula-css/core——将轴相关问题报告为 TAB-E120,将不安全的 dynamicProperties[*].syntax(通配的 '*',它将无法校验任何内容)报告为 TAB-E127。
validateTokens()
export interface ValidateOptions {
/** Reference "now" for expiry/removeAfter checks. Defaults to the current date. */
readonly now?: Date;
}
export interface ValidationResult {
readonly ok: boolean;
readonly diagnostics: readonly Diagnostic[];
}
export function validateTokens(
ast: TokensFile,
config: TabulaConfig,
options?: ValidateOptions,
): ValidationResult;Schema 与值语义的门禁。在 parseTokens() 之后运行,执行每一项结构性检查(P1–P15:深度为 2 的嵌套、已知命名空间、短横线命名的令牌名、必需的 $description ≥ 20 字符、禁止的 $ref、别名深度 ≤ 1 等),以及概念中描述的每一项值语义检查:轴的完备性(TAB-E113——每个已声明的轴成员都需要一个字面量,没有回退值)、跨所有轴组合的 contrastWith(TAB-E153)、按命名空间划分的维度值域(TAB-E111、TAB-E155–E157)、duration/opacity/fontWeight/z 的值域、type/shadow 复合体(TAB-E162/E163)、别名图、例外(exception)文书要求(TAB-E140/E141——理由 ≥ 40 字符且非套话,ISO 到期日不超过 12 个月),以及类名唯一性(TAB-E112)。
import { parseTokens, parseConfig, validateTokens } from '@tabula-css/tokens';
const { ast } = parseTokens(tokensJson);
const { config } = parseConfig(configJson);
const { ok, diagnostics } = validateTokens(ast, config, { now: new Date('2026-07-22') });resolveTokens() 与轴辅助函数
export function resolveTokens(ast: TokensFile, config: TabulaConfig): ResolvedModel;
export function axisSignature(value: unknown, ast: TokensFile, depth?: number): readonly string[];
export function enumerateCombos(axes: readonly string[], config: TabulaConfig): AxisState[];
export function defaultCombo(axes: readonly string[], config: TabulaConfig): AxisState;
export function resolveRaw(value: unknown, combo: AxisState, ast: TokensFile, depth?: number): unknown;
export function resolveLiteral(token: Token, combo: AxisState, ast: TokensFile): ResolvedLiteral;resolveTokens() 是轴解析器与词汇表合成器:它跟随每一个别名,将每一个轴映射展开为完整的字面量矩阵,并合成出候选词汇表(命名空间 × 工具族 × 令牌,加上 STATIC_UTILITIES 与已声明的例外),附带每个候选项预期的声明、槽位与排名。它假定该 AST 已经通过了 validateTokens()。axisSignature() 报告某个令牌的值在哪些轴上变化;enumerateCombos()/defaultCombo() 生成轴组合的笛卡尔积及其默认成员;resolveRaw()/resolveLiteral() 在某个特定组合下解析一个原始 $value(或整个令牌)。
export interface ResolvedModel {
readonly profileId: string;
readonly config: TabulaConfig;
readonly tokens: readonly ResolvedToken[];
/** The synthesized vocabulary, in ascending rank order. */
readonly candidates: readonly CandidateClass[];
readonly customProperties: readonly CustomPropertyDef[];
/** Declared variant products (contract J22): chain prefix → sorted family names, `"*"` pre-expanded. */
readonly variantProducts: Readonly<Record<string, readonly string[]>>;
/** The materialized variant chains, sorted ascending by `(effectiveRank, class)`. */
readonly chainCandidates: readonly ChainCandidate[];
readonly diagnostics: readonly Diagnostic[];
}
export interface ChainCandidate {
/** Full canonical class, e.g. `"hover:bg-accent-hover"`. */
readonly class: string;
/** Canonical ascending-rank, colon-joined chain prefix, no trailing colon: `"hover"` | `"sm:hover"`. */
readonly chainPrefix: string;
/** The base utility class name the prefix is applied to. */
readonly utility: string;
/** Σ of the chain's variant ranks. */
readonly conditionRank: number;
/** `conditionRank * CONDITION_RANK_SCALE + base class rank` — the merge's total order. */
readonly effectiveRank: number;
/** Exception paperwork, present only for an exact-class chain grant (`except add --chain`). */
readonly exception?: ExceptionMeta;
}v0.2 中出货的变体封闭性修复:resolveTokens() 读取配置中已声明的变体产物,把每一个 chainPrefix × family 产物展开为一个 ChainCandidate,并按 (effectiveRank, class) 顺序把它们发出到 chainCandidates 中。 @tabula-css/preset 的 buildChainLayer() 与 @tabula-css/registry 的生成器 都会原样读取这个数组——预设用它来发出每条链一条字面量 CSS 规则,生成器用它在 registry.json 中记录 variantProducts/chainCount。
生成器
export function emitThemeCss(model: ResolvedModel): string;
export function emitResolvedJson(model: ResolvedModel): string;
export function emitVocabulary(model: ResolvedModel): string;
export function emitTypes(model: ResolvedModel): string;
export function ambientBaselineDecls(model: ResolvedModel): { decls: Array<[string, string]>; missing: string[] };
export function checkAmbientBaseline(model: ResolvedModel): Diagnostic[];四个纯粹的 ResolvedModel → string 函数,分别对应一个生成产物:emitThemeCss(@property 声明加上 @layer tabula.tokens 的根级/属性/媒体轴代码块)、emitResolvedJson(扁平的、完全字面量化的 tokens.resolved.json)、emitVocabulary(按排名顺序每行一个合法类),以及 emitTypes(types.d.ts 中的 TabulaUtility/TabulaVariant/TabulaClass 联合类型)。
自 v0.2 起,emitResolvedJson 会原样保留每一个设计令牌的出处标记——$deprecated、$extensions(包括像 com.example.figma 这样的外部厂商命名空间引用),以及一个记录源 $value 曾是单一顶层别名引用时的 $alias 标记——因此一份已提交的 .tabula/ 能够重建出构建它所用的源配置档案。参见 迁移 § 从 .tabula/ 恢复一份配置档案。
ambientBaselineDecls() 计算 base 层级的环境基线(按照概念 § 配置档案层级,每个可继承的、由配置档案所拥有的属性都在 :root 上设置一次),并报告任何无法溯源的项。checkAmbientBaseline() 将非空的 missing 列表转化为 TAB-E220 错误诊断——在 strict 层级下这是一次空操作,因为其排版闭包已使该基线变得多余。
颜色
export interface Rgb { readonly r: number; readonly g: number; readonly b: number; readonly a: number; }
export type ColorParse =
| { readonly ok: true; readonly srgb: true; readonly rgb: Rgb }
| { readonly ok: true; readonly srgb: false } // valid CSS colour, not sRGB-resolvable here
| { readonly ok: false };
export function parseColor(input: string): ColorParse;
export function relativeLuminance(c: Rgb): number;
export function contrastRatio(a: Rgb, b: Rgb): number;一个小巧、自包含的 CSS 颜色解析器(十六进制、rgb()/rgba()、hsl()/hsla()、一组常见的命名颜色、transparent),外加 WCAG 2.x 的相对亮度与对比度计算,供 validateTokens() 的颜色值域与 contrastWith 检查使用。广色域函数(oklch()、oklab()、lab()、lch()、color())会被解析为语法有效(srgb: false),但不会被解析到 sRGB 通道——这正是校验器得以区分“根本不是颜色”(TAB-E150)与“是有效颜色,但无法确认其是否属于 sRGB 色域”(TAB-E151)的关键。
import { parseColor, contrastRatio } from '@tabula-css/tokens';
const surface = parseColor('#0b0b0c');
const text = parseColor('#ffffff');
if (surface.ok && surface.srgb && text.ok && text.srgb) {
contrastRatio(surface.rgb, text.rgb); // ≥ 1, WCAG-comparable
}参见
@tabula-css/core—validateTokens()/resolveTokens()据以校验的冻结表与错误目录。@tabula-css/registry— 消费ResolvedModel,从 Tailwind 编译后的 CSS 中推导registry.json。@tabula-css/preset— 消费ResolvedModel(以及emitThemeCss())来组装 Tailwind 入口样式表。- 快速上手 — 完整的令牌编写演练。
- 概念 — 局部性、主题化轴,以及配置档案层级。