跳转到内容

架构总览

XiHan.UI 是一个 pnpm + turbo 的 monorepo。它的组织方式只服务于一件事:让「组件的行为」独立于「渲染它的框架」存在

一个组件的四份产物

以对话框为例,dialog 这个组件在仓库里落成四处:

产物位置内容
无头内核packages/engine/headless/src/dialog/解剖、状态机、键盘规格表、connect
Vue 组件packages/adapters/vue/src/components/dialog/XhDialogRoot 等一组 defineComponent
自定义元素packages/adapters/web-components/src/elements/dialog.ts<xh-dialog>,Light-DOM 行为宿主
皮肤packages/design/styles/css/dialog.css纯 CSS,按 data-* 选中

四份里只有第一份包含逻辑。后三份分别回答「怎么把属性挂到 Vue 的 vnode 上」「怎么把属性挂到作者手写的 DOM 上」「这些属性长什么样」。

分层与依赖矩阵

层级越低越基础,只能向下依赖。这套拓扑写在 tooling/eslint-config/src/layers.json 里,由 dependency-cruiser 在 pnpm boundaries 时强制,不靠自觉。

可依赖
1kernel—(零运行时依赖)
1machinekernel(零运行时依赖)
1tokens
1icons
2behaviorkernel machine
2positionkernel
2code-highlightkernel
2chat-streamkernel
2markdownkernel
3headlesskernel machine behavior tokens
3styles—(纯 CSS,不得依赖任何 JS 包)
3backgroundskernel behavior
4vue / web-componentskernel machine behavior headless position code-highlight tokens backgrounds

除分层外还有三条硬规则,同样由门禁执行:

  • 库包的运行时代码不得引第三方。 唯一登记在案的例外是 @internationalized/date,只有 headless 的日期族在用。
  • styles 是纯 CSS。 它不依赖任何 JS 包,因此可以脱离整个 JS 层单独使用。
  • 依赖版本只从 workspace catalog 取。 包内一律写 catalog:workspace: 协议引用,不得内联版本号。

一次交互经过哪些层

以「点击对话框的触发器」为例:

用户点击


适配器把 DOM 事件交给 connect 产出的 onClick        (vue / web-components)


service.send({ type: 'TRIGGER.CLICK' })            (machine)


状态机转移 closed → open,执行 entry 动作           (machine)

   ├─► 行为原语接管:锁滚动、建焦点域、压入层栈      (behavior)
   ├─► 浮层族还会请定位引擎算坐标                    (position)


适配器重新读 connect,把新的 aria-* / data-* 铺到部件上


皮肤按 [data-state='open'] 命中新规则,动画播放      (styles)

关键在于中间那三步与框架无关。Vue 适配器和 Web Components 适配器各自只负责最外两步。

包一览

按职责分四组。详细依赖关系见包与依赖关系

内核与原语

职责
@xihan-ui/kernel结构原语:解剖、mergePropsnormalizeProps、Scope、层栈、诊断通道
@xihan-ui/machine薄状态机运行时:createMachine、解释器契约、受控值绑定
@xihan-ui/behavior交互行为原语:消隐层、焦点域、滚动锁、进出场、集合导航、typeahead
@xihan-ui/position浮层定位引擎,自研,零第三方依赖

组件与适配器

职责
@xihan-ui/headless102 个组件的解剖 + 状态机 + connect,无样式、无框架
@xihan-ui/vueVue 3 适配器
@xihan-ui/web-componentsWeb Components 适配器,自研响应式基类

表现

职责
@xihan-ui/tokens设计令牌(DTCG 源)与主题运行时(明暗 / 品牌 / 密度 / 对比度 / 书写方向)
@xihan-ui/styles默认皮肤,按 @layer 分层的纯 CSS
@xihan-ui/icons图标集

内容与效果

职责
@xihan-ui/chat-streamAI 协议内核:SSE 读取 → 协议归一 → parts 归约 → 会话 store
@xihan-ui/markdown流式 Markdown 渲染内核,增量切块 + 稳定 key
@xihan-ui/code-highlight代码着色,自研粗粒度词法器
@xihan-ui/backgroundsWebGL2 背景效果与数据驱动粒子点云

tooling/* 下还有构建、lint、tsconfig、测试与门禁脚本等内部包,一律不发布。

目录结构

ui/
├── packages/            # 对外发布的库包,按角色分四组
│   ├── adapters/        # vue · web-components——你选一个
│   ├── design/          # tokens · styles · icons——外观
│   ├── features/        # markdown · chat-stream · backgrounds——按需自选
│   └── engine/          # kernel · machine · behavior · position · code-highlight · headless
├── tooling/             # 内部构建与质量工具
│   ├── build/           # 打包配置与 exports 回写
│   ├── eslint-config/   # lint 规则 + 分层拓扑事实源
│   ├── stylelint-config/
│   ├── testing/         # 一致性 / 无障碍 / 定位三套判据的运行时
│   └── scripts/         # 门禁脚本
└── apps/
    ├── playground-vue   # Vue 适配器演示
    └── playground-wc    # Web Components 适配器演示

两个 playground 覆盖同一批组件,是对照两套适配器行为的主要手段。

技术选型

位置选型
语言TypeScript,ESM only(不提供 CJS)
运行时要求(消费端)Node ≥ 18;浏览器为 2024 年常青版(皮肤用了 @layercolor-mixoklch
运行时要求(开发本仓)Node ≥ 24、pnpm ≥ 11
构建tsdown(库包)+ turbo(任务编排)
样式原生 CSS,@layer 分层,oklch 色彩空间,无预处理器
测试vitest(jsdom)+ Playwright(真实 Chromium)
发布changesets,全部库包同属一个 fixed 版本组

下一步

Released under The MIT License