公告

👇公众号👇

欢迎大家关注&私信交流

Skip to content

AI 写 wot-ui 老踩坑?我把组件库文档做成了离线知识库 + MCP + CLI

大家好,我是不如摸鱼去,wot-ui 的发起人,欢迎来到我的 AI Coding 分享专栏。

好久不见了,各位,最近忙着在做 wot-ui v2,好久没写文章了,今天分享一下我们为 wot-ui v2 提供的 cli 工具,wot-ui v2 即将迎来正式版,敬请期待。

这篇文章想聊的,不是“我又写了一个命令行工具”,而是另一个我最近越来越强烈的感受:

很多时候,AI 写不好 UI,不是因为它不会写,而是因为它根本不知道你这套组件库到底能不能这么写。

这个问题我见得太多了。大模型能很快把页面搭出来,但经常会在这些地方翻车:

  • props 记错了
  • events 名字猜错了
  • slot 用法写得像那么回事,其实压根不对
  • 主题变量默认值写成“模型想象中的样子”
  • 最难受的是,最后还没法快速定位你项目里到底哪一行有问题

说白了,光靠“问 AI”这条路,太飘了。

直到我看到 ant-design 的 cli 工具 ant-design-cli,原来可以这样玩:与其继续堆 prompt,不如直接把组件库知识做成工具。

这也就是 @wot-ui/cli 想做的事。它把 wot-ui v2 的组件知识整理成离线数据集,再以 CLI 和 MCP Server 两种形态提供出来。仓库定位可以先看 README.md

换句话说,我不是想让模型“更会猜”,而是想让它少猜一点,最好别猜


为什么要做这个东西

如果你平时也会拿 AI 写页面,应该能很快共鸣。

让模型生成一个按钮、弹窗、表单,不难。难的是让它写出来的代码,真的符合你项目正在用的那一版组件库约束。组件文档分散、示例零碎、版本持续变化,这些信息对人类开发者来说还能边查边补,对模型来说就很容易“凭印象发挥”。

最典型的几个坑:

  • 文档里有 props,但 demo 里又藏着另一套更真实的用法
  • CSS 变量默认值在文档没写全,实际要去 SCSS 源码里找
  • 在线抓文档这件事本身就不稳定,今天抓到的和明天抓到的可能都不一样
  • 即使问答答对了,也解决不了“你本地项目里具体哪一行写错”这个问题

所以 @wot-ui/cli 从一开始就不是单纯的“知识查询器”,它更像一条完整链路:

  • 先把组件知识离线化
  • 再把这些知识变成稳定 API
  • 最后给人和 Agent 都准备好可调用入口

这里的两个入口分别是:

  • CLI:给开发者、脚本、CI 用,方便复现和回归
  • MCP Server:给 AI 客户端和 Agent 用,走结构化输入输出

我比较喜欢把这件事理解成一句话:

文档是给人看的,工具才是给机器稳定调用的。


在 @wot-ui/cli 里,数据集不是副产物

很多项目会把“抽出来的数据”当成构建过程里的中间结果,但在 @wot-ui/cli 里,我反而把它当成核心 API。

原因很简单。只要你希望模型稳定地理解一个组件库,那最不该依赖的,就是临时抓取网页和上下文拼接。

所以这里有几个很明确的原则:

  • 离线优先:运行时不依赖网络,也不依赖上游仓库当下是什么状态
  • 结构化优先:能用 JSON 表达的内容,就别再让模型二次概括一遍 markdown
  • 一套内核,多入口复用:CLI 和 MCP 共用同一套加载、查询、扫描逻辑
  • 输出可控:同一能力支持 text/json/markdown 三种格式,既照顾人,也照顾机器

元数据和报告结构定义在 types.ts,里面几个核心对象基本就能把整个设计思路串起来:

  • ComponentMeta:组件知识本体,包含 props、events、slots、cssVars、demos、doc
  • LintReportUsageReportDoctorReport:项目分析阶段的结构化输出

如果用一句更直白的话来说,就是:

先把“组件库知识”压成一份可靠的数据,再讨论怎么给人和 AI 用。


整体架构,其实就三层

我把 @wot-ui/cli 拆成了三块:数据生成、运行时内核、产品入口。

1. 数据生成:把上游文档和源码吃进去

离线数据来自 wot-ui/wot-ui 仓库,提取范围见 README.md

  • docs/component/*.md
  • docs/guide/changelog.md
  • src/uni_modules/wot-ui/components/*/index.scss

提取脚本入口是 scripts/extract.ts,最后输出到:

  • data/v2.json
  • data/versions.json

这两个文件会跟着 npm 包一起发布,配置见 package.json

2. 运行时内核:版本解析、数据加载、组件查询

这层其实很克制,没有做太多花活,链路非常短:

  • resolveVersion:处理 v2/latest 这类 alias,见 version.ts
  • loadMetadataFile:读取离线数据文件,见 loader.ts
  • findComponent / listComponents:组件查找和列表能力,见 metadata.ts

3. 产品入口:CLI 和 MCP 一起对外

  • CLI 入口:src/index.ts,见 index.ts
  • 命令注册:src/app.ts,见 app.ts
  • MCP Server:src/mcp/server.ts,见 server.ts
  • MCP tools:src/mcp/tools.ts,见 tools.ts

对应关系画出来就是下面这样:

mermaid
flowchart LR
  subgraph Upstream[wot-ui/wot-ui]
    A1[docs/component/*.md]
    A2[docs/guide/changelog.md]
    A3[src/.../components/*/index.scss]
  end

  Upstream --> B[scripts/extract.ts]
  B --> C[data/v2.json]
  B --> D[data/versions.json]

  subgraph Runtime[open-wot runtime core]
    E[resolveVersion]
    F[loadMetadataFile]
    G[findComponent / listComponents]
    H[analyzeUsage / lintProject / diagnoseProject]
  end

  C --> F
  D --> E
  E --> F
  F --> G

  subgraph Products[product surfaces]
    P1[CLI: wot ...]
    P2[MCP tools: wot_info/wot_lint/...]
  end

  P1 --> Runtime
  P2 --> Runtime

这个结构有个我自己很喜欢的点:产品入口可以继续长,但底下那套数据和查询内核不用反复重造。


最关键的一步:把“文档 + 源码”整理成结构化知识

这一块才是整个项目最值钱的地方。

因为如果数据本身不靠谱,CLI 做得再花、MCP schema 写得再漂亮,最后也只是把错误包装得更规整一点而已。

所以提取脚本的原则很明确:能从源码拿真值,就别先信文档;文档可以补充,但尽量别当唯一事实来源。

从 markdown 里提取 props、events、slots 和 demo

脚本会解析 markdown 的章节和表格,把这些常见信息抽出来:

  • Attributes / Events / Slots 转成结构化数组
  • code fence 抽成 DemoMeta[]
  • 原始文档内容保留在 doc 字段里,供 doc 命令和 MCP 的 wot_doc 直接输出

相关实现集中在 extract.ts,比如 parseMarkdownTableparsePropsparseEventsparseSlotsparseDemos

CSS 变量优先从 SCSS 拿,不跟文档“赌运气”

这一点我还挺在意的。

因为很多 UI 组件库里,CSS 变量默认值往往比 props 更容易滞后。文档会补,但不一定总是第一时间补齐;而真正能决定主题效果的,往往还是源码里那行 var(--wot-xxx, defaultValue)

所以 @wot-ui/cli 的策略是:

  • 先从组件 index.scss 里解析 var(--wot-xxx, defaultValue)
  • 再结合源码注释生成描述
  • 如果 SCSS 里没拿到,再回退到 markdown 的 CSS 变量 表格

对应实现可以看:

这一步解决的,其实不是“抽数据”本身,而是让主题相关知识从“可能对”变成“尽量有据可查”。


为什么我还是很看重 CLI

很多人一提 AI 工具链,第一反应就是 MCP、Agent、协议、工作流。CLI 反倒容易被看成“传统配角”。

但我自己做下来越来越觉得,CLI 不能少,而且最好先做好。

因为它天然就有三个价值:

  • 它是验证入口,不用依赖任何 MCP 客户端就能复现结果
  • 它是回归入口,输出稳定,适合测试和 diff
  • 它是自动化入口,本地脚本和 CI 都能直接接

@wot-ui/cli 的 CLI 命令清单可以在 README.md 里看到,主要包括:

  • 组件知识查询:listinfodocdemotokenchangelog
  • 项目分析:doctorusagelint
  • MCP 启动:wot mcp

一个我觉得很实用的小设计:统一输出格式

同一类能力支持三种输出:

  • --format text
  • --format json
  • --format markdown

对应类型定义在 types.ts,README 说明见 README.md

这个设计看起来不大,但挺省心。人眼排查的时候用 text,脚本消费的时候用 json,想直接塞到文档或对话上下文里时用 markdown,不用来回再包一层。

看一个真实的 token JSON 输出

相比 infolisttoken 的 JSON 更适合放到文章里,因为它足够小,但也足够能说明“结构化输出”这件事到底有多实用。

bash
pnpm exec tsx src/index.ts token Button --format json

输出节选如下:

json
{
  "name": "Button",
  "cssVars": [
    {
      "name": "--wot-button-primary-bg",
      "defaultValue": "$primary-6",
      "description": "主类型背景色"
    },
    {
      "name": "--wot-button-primary-bg-active",
      "defaultValue": "$primary-7",
      "description": "主类型背景激活色"
    }
  ]
}

对应实现见 token.ts

对我来说,这种输出最大的意义在于:模型不用再看一大段 prose 去猜“这个 token 大概是什么意思”,它直接拿 JSON 就行。


MCP 解决的,不是“换个壳”,而是“让能力可协议化”

如果只从表面看,MCP Server 好像只是把 CLI 包了一层。

但它真正有价值的地方,其实是把工具调用这件事变成了可以被客户端理解、可以被约束、可以被安全声明的行为。

@wot-ui/cli 里,MCP tools 的定义和实现都放在 tools.ts,里面几个比较核心的工具包括:

  • wot_info:返回组件的 props、events、slots、cssVars
  • wot_doc:直接输出原始 markdown 文档,避免模型转述时失真
  • wot_demo:列出 demo,或者返回指定 demo 代码,做 few-shot 很稳
  • wot_lint:扫描本地项目并返回结构化报告

MCP 这层最重要的几个点,我会总结成这样:

  • 输入是 schema 化的,比如 componentversiondir
  • 输出可以是严格 JSON,也可以是原始 markdown
  • 工具行为可以显式声明,比如只读还是 open-world

最小 MCP 配置

把下面配置加到支持 MCP 的客户端里,就能通过 stdio 启动:

json
{
  "mcpServers": {
    "wot-ui": {
      "command": "wot",
      "args": ["mcp"]
    }
  }
}

示例也可以参考 README.md

为什么专门标注 open-world

wot_lint 会访问本地文件系统,所以它在 MCP annotations 里标成了 open-world,相关实现见 tools.ts

这件事听起来像个细节,但其实很重要。因为一旦工具开始“看你的本地项目”,那它就不再只是普通知识查询了,客户端在权限和安全层面应该有明确预期。

我挺希望 MCP 生态里这类标注能被越来越认真地使用起来。否则很多工具看起来都叫“查一查”,实际有的只是读离线包,有的已经在扫你整个工程目录了,这两者差别还是很大的。


真正让我觉得它像“工具链”的,是后面的 usage、lint、doctor

如果 @wot-ui/cli 只做到组件知识查询,其实已经能解决一部分问题了。

但还不够。

因为 AI 最容易出问题的地方,常常不是“它不知道”,而是“它已经改进项目了,但改得不对”。这时候你需要的,不是再问一遍文档,而是基于你本地工程的事实做检查。

这也是为什么我后来把 usagelintdoctor 一并补上了。

usage:先看看项目里到底怎么用的

usage 会扫描 .vue 文件,做几件事:

  • @vue/compiler-sfc 解析 SFC
  • 统计模板中的 <wd-*> 标签出现次数和所在文件
  • 收集脚本区里和 wot 相关的 import

实现见 scanner.ts

这个命令很适合做摸底。比如你准备升级、做规范治理,或者只是单纯想知道“项目里哪些组件被用得最多”,它都挺顺手。

lint:把元数据真正用起来

lint 的意思不是“再造一个 ESLint”,而是把组件元数据转成更贴近 wot-ui 使用约束的规则。

当前内置规则包括:

  • unknown-component:模板里有标签,但元数据里查不到
  • button-contentwd-button 既没有可见文字,也没有 icon
  • deprecated-prop:基于元数据标出的废弃 prop,目前先落在 wd-button

实现见 scanner.ts,输出结构定义在 types.ts

真实输出长这样:

json
{
  "scannedFiles": 1,
  "issues": [
    {
      "file": "pages/index.vue",
      "line": 3,
      "rule": "unknown-component",
      "severity": "warning",
      "message": "Unknown wot-ui component tag: wd-not-exist"
    },
    {
      "file": "pages/index.vue",
      "line": 2,
      "rule": "button-content",
      "severity": "warning",
      "message": "wd-button should include visible text content or an icon attribute."
    }
  ]
}

有了这种报告,AI 才算真的有机会从“生成代码”走到“对着文件和行号继续修代码”。

doctor:别上来就分析,先检查前提对不对

doctor 负责做基础环境诊断,比如:

  • 有没有 package.json
  • 有没有安装 @wot-ui/ui
  • vue、uni-app、typescript 这些依赖是否齐全
  • node_modules 是否存在

实现见 project.ts

这个命令的作用很朴素,但很有必要。很多分析工具看起来“没扫出结果”,最后并不是规则有问题,而是目录压根就不满足运行前提。


我最想支持的,其实是这一条闭环

如果把 @wot-ui/cli 当成一个 AI Coding 工作流里的部件,我最希望它支持的是下面这个过程:

  1. 组件名不确定,先跑 wot_listwot list 拿候选集合
  2. 组件约束不确定,先用 wot_info 看 props、events、slots、cssVars
  3. 用法还没把握,继续用 wot_demo 拉对应 demo 做参考
  4. AI 根据约束和 demo 生成或修改代码
  5. 改完以后,用 wot_lintwot lint 去扫本地项目
  6. 根据 lint 报告继续修,直到结果收敛

这一套下来,AI 才不是“答完问题就跑了”,而是真的参与了一轮可回放、可定位、可迭代的工程过程。

对应的闭环图如下:

mermaid
flowchart LR
  A[约束获取: wot_info / wot_token] --> B[代码生成/修改]
  B --> C[事实校验: wot_lint]
  C -->|issues| B
  C -->|clean| D[合入/发布]

顺手附一段我自己会用的 Agent 提示词

如果你在支持 MCP 的客户端里配项目规则,下面这段提示词基本可以直接拿去改一改:

text
你是 wot-ui v2 的工程助手。写组件相关代码前必须先通过工具获取确定性信息,不得凭记忆猜测 props、事件、slot、CSS 变量。

规则:
1) 组件名不确定时,先调用 wot_list 获取候选组件。
2) 编写/修改某组件用法前,先调用 wot_info(component) 获取 props/events/slots 约束。
3) 如果要写示例或不确定用法,先调用 wot_demo(component) 获取最接近目标的 demo 代码。
4) 如果涉及样式主题或 CSS 变量,先调用 wot_token(component) 获取变量与默认值,再给出修改建议。
5) 修改完成后必须调用 wot_lint(dir) 扫描项目,基于报告定位并修复问题;除非用户明确要求跳过。

输出要求:
优先给出可复制的代码片段与修改点列表;遇到冲突时以工具返回信息为准。

我自己会比较喜欢这种“工具优先”的约束方式。不是因为 prompt 不重要,而是因为 prompt 说到底还是在提醒模型“你要认真点”,工具调用才是在真正给它确定性信息。


现在的边界,也顺便说清楚

@wot-ui/cli 现在还很克制,边界在 README.md 里也写得比较明确:

  • 当前只支持 wot-ui v2
  • usagelint 主要聚焦 .vue 文件里的 <wd-*> 标签及相关 import
  • CSS 变量提取优先走 SCSS,必要时才回退到 markdown 表格

如果后面继续扩,我觉得方向还是那句老话:继续围绕“数据集就是 API”来做。

比如这些方向都挺自然:

  • 让多版本数据集并存,不再只盯着 v2
  • 把元数据驱动的 lint 规则扩到更多组件
  • 给提取脚本和 loader 增加更稳的回归校验,早点暴露上游结构变化

我不太想把它做成一个什么都想包进去的大而全平台。至少现在阶段,我更看重的是这条链路够不够稳,输出契约够不够清楚。


最后

回过头看,@wot-ui/cli 真正想解决的问题,其实一直只有一个:

怎么把组件库知识,从“人类读得懂的文档”,变成“机器也能稳定调用的能力”。

CLI 解决的是复现和回归,MCP 解决的是协议化调用,usage/lint/doctor 解决的是和本地工程事实闭环。它们放在一起,才比较像一套完整工具链。

如果你也在做组件库、AI Coding 工具,或者正在折腾“怎么让模型别乱猜 API”,那我觉得这个方向还是挺值得试一试的。

至少对我来说,这比继续和 prompt 斗智斗勇省心多了。

上次更新于: