Skip to content

@tabula-css/tokens

Tabula 的扁平 DTCG 配置档案解析器、值语义校验器、轴解析器与生成器。

安装

@tabula-css/tokens 不属于快速上手指南中需要直接安装的包——它是 @tabula-css/registry(以及借由它,@tabula-css/cli)的传递依赖,而大多数项目正是用后者把令牌转换为一次构建。只有当你在围绕令牌流水线本身构建自定义工具时——例如一个在不运行完整 tabula build 的情况下校验令牌文件或将其解析为 CSS 的脚本——才需要直接安装它:

bash
npm install @tabula-css/tokens

概览

在这里,一个设计令牌(design token)不再只是 JSON,而是变成系统其余部分可以信任的值。@tabula-css/tokens 实现了快速上手中描述的构建流水线第 1–5 步:解析令牌与配置文件,校验值语义(不仅是形状——一个 { value: 0, unit: "px" } 的 font-size 在 JSON 层面是合法的,但在这里会是一个构建错误),将轴映射与别名解析为字面量矩阵,并生成四个 W1 产物(theme.csstokens.resolved.jsonvocabulary.txttypes.d.ts)。它产出的已解析模型——合成出的词汇表、其预期的声明、自定义属性表——正是 @tabula-css/registry 用来从 Tailwind 真实编译输出中推导 registry.json 的输入。

导出

buildProfile()

ts
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 级别的诊断,就不会返回 modelartifacts。如果你只想要“输入令牌 + 配置,输出产物或诊断”,这就是唯一需要调用的函数。

ts
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()

ts
export interface ParseResult {
  readonly ast: TokensFile | null;
  readonly diagnostics: readonly Diagnostic[];
}
export function parseTokens(json: string): ParseResult;

将令牌文件字符串解析为 AST。这一阶段只保证输入是 RFC 8259 JSON 且顶层为对象——不允许 JSON5、注释、尾随逗号或前导 BOM——否则返回 TAB-E101ast: null。值语义由 validateTokens() 单独校验。

validateConfig() / parseConfig()

ts
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()

ts
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——每个已声明的轴成员都需要一个字面量,没有回退值)、跨所有轴组合的 contrastWithTAB-E153)、按命名空间划分的维度值域(TAB-E111TAB-E155E157)、duration/opacity/fontWeight/z 的值域、type/shadow 复合体(TAB-E162/E163)、别名图、例外(exception)文书要求(TAB-E140/E141——理由 ≥ 40 字符且非套话,ISO 到期日不超过 12 个月),以及类名唯一性(TAB-E112)。

ts
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() 与轴辅助函数

ts
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(或整个令牌)。

ts
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/presetbuildChainLayer()@tabula-css/registry 的生成器 都会原样读取这个数组——预设用它来发出每条链一条字面量 CSS 规则,生成器用它在 registry.json 中记录 variantProducts/chainCount

生成器

ts
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(按排名顺序每行一个合法类),以及 emitTypestypes.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 层级下这是一次空操作,因为其排版闭包已使该基线变得多余。

颜色

ts
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)的关键。

ts
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/corevalidateTokens()/resolveTokens() 据以校验的冻结表与错误目录。
  • @tabula-css/registry — 消费 ResolvedModel,从 Tailwind 编译后的 CSS 中推导 registry.json
  • @tabula-css/preset — 消费 ResolvedModel(以及 emitThemeCss())来组装 Tailwind 入口样式表。
  • 快速上手 — 完整的令牌编写演练。
  • 概念 — 局部性、主题化轴,以及配置档案层级。

Released under the MIT License.