
Astryx 主题编译架构从一次定义到运行时与构建产物的统一 CSS 管线【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx导读本文围绕 Astryx 设计系统中「一次主题定义、多处平台输出」的核心架构记录展开完整讲解主题从defineTheme()作者输入到 Web CSS 的编译链路共享编译器generateThemeRules如何生成规范 CSSThemeProvider 如何在运行时注入样式CLI 的theme build如何把规则落盘为静态产物以及 adaptation条件适配、local-token 所有权、derived-var 展开等边界不变量是如何被实现与验证的。读完本文你将掌握 Astryx 主题编译的完整流水线、关键不变量INV1–INV12的工程含义以及运行时与静态构建保持一致性的底层机制。一次定义、多种输出主题编译的系统模型Astryx 的主题系统遵循一条核心承诺架构记录中的INV1 — One definition can have many outputs主题作者只维护一份主题定义Web 运行时按需编译它、静态构建提前编译它两者必须产出相同的 CSS 行为未来的原生编译器即便产出 style object 而非 CSS也应从同一份DefinedTheme出发INV9。平台相关细节留在各平台编译器内部共享作者输入中不允许出现 CSS 专属的 selector、layer、scope 等概念。当前 Web 编译器的工作流程可概括为六步见 docs/architecture/theme-compilation.mdgenerateThemeRules把 portable tokens、theme-local tokens 与组件 override 转换为 CSS 规则有序 adaptation 规则在根规则之后编译为独立 media 块块之间保持作者顺序、绝不合并或做 value-diff同一代码路径追加 state 规则、media-surface 规则、scope 与 layerThemeProvider在主题未预先构建时挂载这些规则CLI把规则保存为 CSS并打包相关的 JavaScript 与类型构建后的主题保留 local-token 所有权、有效宽度点、generative-axis 元数据与归一化的有序规则并被标记为已构建Provider 不再重复编译或注入。这一步一以贯之的实现位置在 packages/core/src/theme/generateThemeRules.tsgenerateThemeRules接受一个比DefinedTheme更窄的ThemeRuleSourcetokens、localTokens、components按固定顺序产出规则——先 token 块:scope上的自定义属性再组件 override、prose 元素规则、prop 级颜色/size/weight 覆盖最后通过generateThemeCSS拼装完整的双层 CSS。文件头注释明确写道这是运行时注入style与astryx theme build预编译共用的同一份逻辑。层级与作用域Web 唯一级联契约架构记录要求 Web 只有一个级联契约INV4运行时与构建 CSS 使用相同的 scope、astryx-themelayer、组件 override 与消费者优先级。源码把这条契约落到了三个明确的输出面scope限定所有主题规则被包在scope ([data-astryx-themename]) to ([data-astryx-theme])中即主题名 data 属性作为 scope 起点、任意嵌套主题属性作为 scope 终点保证嵌套主题不会串扰。on-media 规则则使用无to上限的 scope以便触及[data-astryx-media]元素见 generateThemeRules.ts。双层 layergenerateThemeRulesSplit把规则分成两组——:where()开头的 prose 规则放入layer reset零特异性默认值任何 class 样式都能覆盖其余组件/Token 规则放入layer astryx-theme位于 StyleX 层之上主题才能有意重构组件外观。代码注释对此的解释是「prose defaults … sit at reset-layer priority while component overrides sit above StyleX」见 generateThemeRules.ts。运行时注入的 layer 归属ThemeProvider 在useInsertionEffect中创建三个style——--color-data-*默认块进layer astryx-base追加而非前置避免颠倒 layer 顺序prose 块进layer reset组件块进layer astryx-theme见 packages/core/src/theme/Theme.tsx。--color-data-*默认块值得一提generateDataTokenDefaultsCSS产出一个文档级、非主题作用域的:root块放进astryx-base层这样主题自己的--color-data-*覆盖靠 layer 而非特异性取胜且嵌套主题不会重复声明默认值造成 shadowing。CLI 构建路径格式化同一块数据并有测试断言两者字节级一致见 generateThemeRules.ts。保证属性、公共语义变量与私有变量三级输入策略主题作者在组件 target 上写 CSS 属性时编译器按「保证属性 / 公共语义变量 / 通用属性」三种语义分级处理对应架构记录的 INV5–INV8保证属性guaranteed properties顶层声明尽量按原样保留为通用 CSS 属性但当保证属性需要「抵达内部绘制者」时derivedVarRegistry会把它翻译成一个或多个私有变量--_*甚至在直接应用到目标元素会出错时替换源声明replaces: true。这是 INV5「保证属性保持可观测」的机制——实现选择可以变但属性承诺的语义不能变。公共语义变量public semantic custom properties经评审、组件主题契约承认、且没有对应保证 CSS 属性可以表达时作为直接作者输入透传INV7但不允许把私有机制变成公共 API。直接私有--_*输入被契约禁止INV6运行时与构建两个作者路径都必须拒绝而非「产出看似合理的输出」。derivedVarRegistrypackages/core/src/theme/derivedVarRegistry.ts是这套策略的编译后登记表其权威来源是各组件 doc 文件中的theming.derived[]一致性测试保证二者不漂移。以card为例card: [ {property: borderRadius, vars: [--_card-radius]}, {property: padding, expand: container}, ],含义是作者写card.base.borderRadius时编译器同时发出--_card-radius私有变量作者写card.base.padding时走expand: container策略——把 padding 展开为组件作用域公共 token如--astryx-card-padding、--astryx-card-padding-inline、--astryx-card-padding-block-start/end组件通过反向 fallback 链var(--astryx-*, default)消费从而避开与 StyleX 输出的 layer 竞争见 generateThemeRules.ts 的expandContainerPadding。值得注意的细节parsePadding会解析 1–3 值的 padding 简写、逻辑属性与物理 block 长手属性paddingTop→paddingBlockStart但刻意排除paddingLeft/Right——它们是方向相关属性LTR 下 left 是 inline-startRTL 下则相反映射会导致 RTL 下 padding 落到相反边缘因此保留物理语义直接落地generateThemeRules.ts。replaces: true的典型应用是progress-bar-mark的宽高与text-area的paddingInline源属性被丢弃、只发 var因为承载 class 的元素本身不该收到该标准属性值由子元素经 var 消费derivedVarRegistry.ts。另外注册表还维护了废弃组件 key 的别名映射hovercard→hover-card、textarea→text-area等因为重命名后的目标仍发出旧 class主题按旧 key 书写时派生变量仍要能展开见 derivedVarRegistry.ts。派生的组件规则生成一层 lowering 贯通三处generateComponentRules是组件规则生成的唯一函数同时服务于根规则、adaptation 规则与 media-surfaceonDark/onLight规则架构记录称其为「one component-leaf lowering path」。其处理流程generateThemeRules.ts分离常规属性与伪类:开头且值为对象覆盖对每个常规属性查询getDerivedVars收集派生变量声明标记replaces属性与 container 展开若触发了 container 展开把 padding 相关属性换成 component-scoped container token并支持resetInheritedPaddingSpecificity时对更具体的方向 token 补发initial保证 invalid 使var()走 fallback清除根规则里更具体的声明丢弃replaces属性追加派生变量声明最后以 kebab-case 输出声明块伪类规则通过appendPseudoToSelectorList把伪类分发到逗号分隔选择器列表的每一项——CSS 不会把尾随伪类分发到整个列表不重写就会只命中最后一项。伪类处理中还内建了禁用态守卫:where(:not(:disabled,[aria-disabledtrue]))浏览器抑制禁用控件的交互事件但不抑制其 hover 样式主题写的:hover: {backgroundColor}若不加守卫会画到禁用元素上。该守卫与astryx/no-hover-on-disabledlint 规则互为镜像generateThemeRules.ts。同一 lowering 还负责把 prop 级覆盖重新发射到主题层保证公共 prop 语义不被子主题层遮蔽colorprop 覆盖当主题触及 text/heading/link 时为每个命名颜色重发color规则generateThemeRules.tssizeprop 覆盖Text 的size类在astryx-base层而主题的按 type 字号规则在更高的astryx-theme层层叠会让主题静默遮蔽size编译器在同一层、同一特异性、更靠后的位置重发 size 类恢复其 override 语义且只覆盖font-size、保留 line-heightgenerateThemeRules.tsHeadingweightprop 覆盖在作者 type 规则之后按固定 token 映射重发权重类让显式weight可靠覆盖 type/level 默认值generateThemeRules.ts。有序 adaptation宽度断点、条件词汇与 CSS-first 块Adaptation 是 Astryx 主题针对环境条件视口宽度、指针精度、对比度偏好、减少动效偏好的条件式取值系统架构记录以INV12约束其行为根声明先发、adaptation 块保持作者顺序独立、media-surface 覆盖最后发重复条件与后置的「恢复根值」写入必须被精确保留运行时与静态输出使用相同的块。封闭的条件词汇条件字段固定且按 AND 组合packages/core/src/theme/themeAdaptations.ts字段取值说明width{from?, below?}视口宽度区间使用固定命名起点from为含、below为不含pointercoarse \| fine主指点设备精度contrastmore \| less \| no-preference用户对比度偏好motionreduce \| no-preference用户减少动效偏好宽度起点是固定名字sm/md/lg/xl/2xl默认值为 640/768/1024/1280/1536 CSS pxthemeAdaptations.ts。规则结构为{when, value}value可写 typography/color/radius/motion 轴覆盖、portable token、精确 theme-local token 替换与组件写入themeAdaptations.ts。归一化与解析normalizeThemeAdaptations负责合并继承与本地元数据继承规则保序、子规则追加宽度 map 必须完整且严格递增否则抛错themeAdaptations.ts。归一化会拒绝稀疏规则与保留 token 路由条件字段缺省值也会在校验中被拒绝。CSS-first 生成generateAdaptationCSS把每条规则独立编译为media … { scope … { … } }块绝不合并、重排或 value-diffgenerateThemeRules.ts。注释解释了一个微妙点后面故意写回根值的规则必须保留因为它要覆盖前面匹配的同条件规则。prose 规则引用语义变量而非烘焙根值因此 adaptation 的 token 写入无需在每个 media 查询里复制 prose 选择器即可生效generateThemeRules中的val (key) var(${key})模式。Heading 权重在 adaptation 中的处理尤其讲究普通规则块先发且不带继承的权重 fallback把前一条规则的权重带进后一条会在后者条件匹配时错误激活该值随后发一个无条件的「有效根守卫」块恢复公共weightprop最后只按作者顺序重发显式书写的条件权重写入保持 last-write-winsgenerateThemeRules.ts。新旧核心配对ERR_CORE_INCOMPATIBLE 与失败关闭CLI 与 core 独立版本化因此存在「能编译 adaptation 的 CLI 不能编译 adaptation 的旧 core」这一受支持配对架构记录「Change coupling」一节对此有专门论述见 docs/architecture/theme-compilation.md。该配对仅限定于 adaptation 能力不是针对任意旧核心的通用兼容主题无 adaptation 意图 → 构建产物与以往完全一致存在有效规则、自定义宽度 map、或存在但格式错误的 adaptation 元数据→ 均视为意图在产出任何输出前以ERR_CORE_INCOMPATIBLE失败完整的默认宽度 map 且无规则 → 双路径均为 no-op因为这是 core 写到每个解析主题与每个已构建主题上的东西。由于早于 adaptation 的 core 在解析时会抹除adaptations元数据构建过程会记录每次原始defineTheme()输入并关联到它产出的主题即core-interception.mjs的职责给加载中的主题一个defineTheme被包裹的 core记录每个原始输入及其生成轴并报告无法覆盖的加载路径——async fallback 绕过拦截、CommonJS 到达无法包裹的 core 命名空间都会触发失败关闭。只有被选中的主题血缘决定成败图中其他未使用的 adaptive 主题不会让普通构建失败。旧 core 缺失__axes时捕获到的原始 typography/color/radius/motion 元数据会被保留当前核心的子主题扩展该产物时能精确解析部分 adaptation 轴。源码证据见 packages/cli/api/theme/build/build.mjs拦截逻辑与同文件的 capability 检查_generateAdaptationCSS缺失即报ERR_CORE_INCOMPATIBLE。运行时 Provider注入、去重与 built 标记ThemeProviderpackages/core/src/theme/Theme.tsx负责三件事注入未构建主题的 CSS、同步根 color-scheme 属性、提供 context。注入去重injectedThemes集合保证同名主题只生成并注入一次已构建主题theme.__built直接跳过注入——其 CSS 在消费者单独 import 的独立文件里Theme.tsx。这正是架构记录第 6 步「marked so the provider does not compile or inject it again」的落地。未构建主题还会收到一次性性能提示引导作者改用astryxdesign/theme-name/built与theme.css或运行npx astryxdesign/cli theme build file。根同步树中第一个无父 ThemeProvider 会把data-theme驱动color-scheme让滚动条、原生表单控件等浏览器 chrome 跟随模式与data-astryx-theme让 scope CSS 触及 Portal、toast fallback 视口等包装外的元素同步到document.documentElement嵌套 Provider 跳过同步Theme.tsx。--color-data-*默认块是文档级:root块用引用计数管理生命周期——只有最后一个挂载的 Theme 卸载时才移除避免误伤仍挂载的 ProviderTheme.tsx。构建产物保存、打包与可观测元数据CLI 侧packages/cli/api/theme/build/build.mjs是构建路径的所有者它调用同一编译器得到 CSS 块写入theme.css并打包配套 JS 与类型。构建后的主题保留local-token 所有权归一化的localTokensmap 原样并排输出不重写名字或值对应INV11有效宽度点与 generative-axis 元数据归一化的有序规则__built标记与血缘元数据。__adaptationRules在已构建模块中被有意省略见 build.mjs构建产物通过保留的元数据保持可观测。由于生成 JS 与generatedprovenance 记录了解析元数据与 core 版本--check在升级后报 drift 是合法现象不表示行为差异。架构记录还明确了变更耦合规则docs/architecture/theme-compilation.md任何 Web 主题→CSS 的改动必须落在共享编译器里并用运行时挂载与 CLI 构建双路径测试Provider 与 CLI 只允许挂载/打包不得实现自己的主题转 CSS 变换对应INV2、INV3。改动 scope/layer 输出要同时测 source 与 distribution 构建改动 local-token 发射要验证运行时/静态的精确名称对等、原子失败与已构建主题血缘元数据保留。不变量验证矩阵与已知合规差距架构记录用一张验证表把每个不变量映射到证据文件与失败信号docs/architecture/theme-compilation.md。关键对应关系如下不变量证据失败信号INV1/2/3编译器导入与运行时/构建对照 fixtures运行时与构建使用不同的主题→CSS 逻辑或产出不同规则INV4generateThemeRules.test.ts与 source/distribution 级联测试scope 或 layer 顺序随输出路径变化INV5既有逐属性 fixtures部分覆盖保证属性编译了但未产生承诺的可观测效果INV6/7既有 registry 与 CLI 公共变量测试部分覆盖私有变量变得可书写或评审过的公共语义变量构建/运行失败INV11defineTheme.test.ts与build.test.mjslocal-token fixtures运行时/静态输出重写本地名、不一致或在失败后留下部分输出INV12themeAdaptations.test.ts与 CLI adaptation 构建 fixtures规则块被合并/重排/丢弃、surface 失权或运行时/静态 CSS 分叉Built themesTheme 与 CLI 构建测试运行时重编译已构建主题或构建产物缺失规范规则架构记录同样坦率地列出了当前已发布但尚未合规的三处缺口docs/architecture/theme-compilation.md私有作者输入未端到端拒绝themeBuild对直接--_*值在 receipt/log 中报错但仍继续编译并输出校验只查components下的顶层声明嵌套伪类与 media-surface 组件会绕过运行时defineTheme无对等校验直接接受。后续修复须在两条路径的 CSS 生成前递归拒绝直接私有变量并证明运行时/静态对等。嵌套伪类声明绕过派生展开根、adaptation、onDark/onLight.components下的顶层声明都走derivedVarRegistry含replaces与 container 展开但这些 surface 上的嵌套伪类仍直接序列化属性。后续须让嵌套伪类走同一 lowering 路径并为普通映射、replaces、container padding 提供对等 fixtures。保证属性覆盖未机器完备共享 catalog 是规范性的但组件 doc schema 尚无按 target 的guaranteedProperties声明CI 无法证明某个 target 理性支持其选中的 catalog 子集或评审新增。后续须迁移该元数据并对缺少双路径证据的 target/property 对直接失败。记录明确强调这些不变量是管辖实现与评审的已批准规则上述缺口是针对契约的已发布缺陷而非建议行为。此外prefix-independent 的localTokenskey 接受虽获批准但尚未落地——当前编译器仍要求主题派生的原始前缀并用此前缀分类本地引用docs/architecture/theme-compilation.md。可继续深入阅读的仓库资源架构记录本体docs/architecture/theme-compilation.md以及 docs/architecture/README.md 对架构记录写作约定的说明共享编译器核心packages/core/src/theme/generateThemeRules.ts派生变量登记表packages/core/src/theme/derivedVarRegistry.ts条件适配系统packages/core/src/theme/themeAdaptations.ts运行时 Providerpackages/core/src/theme/Theme.tsxCLI 构建与核心拦截packages/cli/api/theme/build/build.mjs 与 packages/cli/api/theme/build/core-interception.mjs一致性测试generateThemeRules.test.ts、themeAdaptations.test.ts、derivedVarRegistry.test.ts以及 CLI 侧的build.test.mjs、build.public-component-vars.test.mjs、build.adaptation-core-compat.test.mjs、build.packed-old-core.test.mjs均在各自目录的test下。【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考