写作约定
demo 单一事实源(D7)
预览与源码都来自 app/examples/**/*.vue 同一个文件——DemoBlock 用 import.meta.glob 双取(组件 + ?raw 源码),不存在「预览一份、贴代码一份」的漂移。
- 文件路径即 demo 名:
app/examples/button/basic.vue→::demo{name="button/basic"}。 - 每个 demo 自包含:自己 import 用到的
@xwink/ui组件与 vue API,不依赖页面上下文。 - 组件页配 2-3 个 demo:一个基础、一个交互、一个边界(disabled / 错误态 / 空态)。
SSR 安全(I8)
示例会 SSR 直出并进 SSG 全站预渲染,构建期与运行期的输出必须一致:
- 禁止随机值(
Math.random)、时间戳(Date.now)、不稳定的v-forkey。 - 初始状态必须确定:交互状态一律从确定值出发(如 v-model 初始空串、loading 初始
false)。 - 「运行期才有的东西」(命令式弹窗、toast)用触发按钮承载,不在初始化时执行。
- 违反会出现 hydration mismatch 或 generate 产物漂移,dev 控制台与 generate 都能抓到。
两级 demo 形式
L0:内联 MDC
props / slot 一览无余的组件(Button / Icon / Input 这类)直接写在正文里:
md
::x-button{variant="primary"}
点我
::
解析链:MDC 标签 → components/content/ 里同名包装组件(转发 attrs/slot)→ @xwink/ui 组件。带脚本上下文的组件(Dialog / Select / Table)不推荐 L0——状态在正文里表达不了。
L1:DemoBlock
需要脚本上下文(v-model 状态、列表数据、事件组合)的演示写进 app/examples/:
md
::demo{name="button/basic" title="按钮变体"}
::
DemoBlock 自带「源码」折叠与「复制」,预览区 SSR 直出——文档站本身就是组件库的 SSR 金丝雀(roadmap I5),个别确认无法直出的场景用 ClientOnly 局部降级并给库记 todo,不顺手改库。
图标登记
demo 里用到的 ph:* 图标必须已在 scripts/gen-docs-icons.mts 的清单里登记(注册表保证离线 SSR 直出)。新增图标:加名字 → pnpm -F @apps/ui-docs gen:docs-icons → 提交生成物。