Yosegi — 从宿主组件构建屏幕
版本: 2026-08-13。 代理工具可能加载过时的技能副本而不作说明。如果此日期早于仓库中
skills/yosegi/SKILL.md的版本,说明您正在阅读过期副本 — 请重新安装后再继续。
Yosegi 将宿主项目的组件转化为可构建屏幕的事实依据,并在实现前将屏幕输出为 Story (CSF) 以供审查。它自身既无渲染环境也无图形界面;您通过宿主的 Storybook 查看效果。在未安装 Storybook 的宿主上,交付物变为纯 React 组件文件 (--target component) — 下方步骤 1 分支说明了流程如何变化。
本文档即为操作流程,您是执行者。CLI(以及等效的 MCP 工具)是其使用的工具。
何时使用
- 从现有设计系统组合屏幕提案或 UI 模型,并以可审查的形式呈现。
- 在实现前,让新屏幕的第一个草稿在 Storybook 中可见。
- 在编写任何 UI 之前,查找宿主有哪些组件及其实际接受的 props。
- 将已批准的 mock Story 转录为真实页面。执行步骤 1 和 2,然后跳至步骤 5。
不适用于:一次性组件修改。
价值所在
注册表职责明确:它是组件 props 的真实来源 — 真实的 prop 名称、真实的枚举选项、哪些是必需的、哪些是插槽 vs children、函数 prop 的调用签名。宿主的组件并非它们所 fork 的库。它们的变体枚举已被重命名,子组件之间的尺寸比例不同,icon prop 接受的是组件引用而非元素,ReactNode prop 是命名插槽而非 children,三个不同的组件共享一个导出名称。这些都无法通过对 React 或上游库的熟悉程度推导出来,而每一项都是如果您按照预期而非注册表实际存在的 prop 编写,就会交付的缺陷。这正是步骤 1 和 2 中 inspect 调用的用途,跳过它们就是让看似合理的 mock 携带缺陷。
注册表不提供屏幕的骨架、组合习惯用语或布局 — 驱动 DataGrid 的网格库 hook 调用、宿主特定的 meta(如 { stickyHeader: true })、组件期望的 wrapper。这些来自宿主自身的 Stories,以及(如果宿主有页面/路由生成器)其 Example 模板(步骤 2)。宿主的模板和 Story 覆盖越丰富,注册表在您实际构建屏幕所需信息中的占比越小 — 这并非缺陷。注册表和宿主自身的示例回答不同的问题,并且各自对其回答的问题都至关重要。
参考资料
在下方步骤要求您查阅时再打开,不要提前。对于 3a 路线(直接编写 Story),可能永远不需要:registry status 和 component inspect 的输出加上本文档正文通常足以构建整个屏幕。但一旦需要,请依据参考资料而非仅凭本文档记忆 — 其细节在此不重复。
| 文件 | 何时阅读 |
|---|---|
references/registry.md | 当 inspect 的输出有无法解释的内容 — 不透明 prop 的 shape: / signature: 行、未文档化的 prop、看起来错误的导入说明符 |
references/cli.md | 当命令或标志未在本文档中说明、CLI 无法运行、或需要 MCP 等效命令时 |
references/screen-json.md | 仅当您选择 Screen JSON 路线(3b)时。格式及其无法表达的内容 |
references/errors.md | 当任何命令打印错误或警告时。每个错误码及其修复方法 |
references/implementation.md | 用于步骤 3 中的元模板,以及步骤 5 之前 |
步骤 1. 构建注册表
Yosegi 需要宿主提供可读的 tsconfig.json(props 来自 TypeScript 类型,这已足够)。Storybook 是 Story 的存放位置以及您确认渲染效果的地方 — 但它并非必需。
如果您无法检测到 Storybook — 没有
.storybook/(或等效)配置目录,宿主package.json中没有storybook脚本,没有index.json可提供给--index— 交付物为纯 React 组件文件而非 Story(screen generate --target component,references/cli.md)。不要自行选择输出位置或审查方法:在构建任何内容之前,询问用户 (a) 组件文件应放在何处,以及 (b) 他们希望如何确认结果(例如,在宿主应用的临时路由上渲染)。Yosegi 不指定审查界面,猜测任一答案都与猜测 prop 一样违反规则。有了这些答案,以下流程保持不变,仅有三处差异:registry build运行时不带--index/--storybook-url(您失去策展,其他不变);在路线 3a 中,您在用户指定的位置编写组件文件;在路线 3b 中,您传递--target component --out <那个位置>.tsx;步骤 4 的 Storybook 检查被宿主类型检查以及用户命名的确认方法所取代。
- 您能运行 CLI 吗?
npx yosegi(或pnpm yosegi、yarn yosegi、bunx yosegi)不带参数会打印所有命令,前提是@yosegi/yosegi已是宿主的依赖项 — 如果尚未安装,请先安装(references/cli.md)。需要 Node.js 20 或更新版本,React + TypeScript 宿主。如果命令失败,请参阅references/cli.md中的“如果 CLI 无法运行”;最有用的操作是读取<data-dir>/registry.json的builtWithCliPath(如果之前会话中已有注册表)。 - 现在就固定一个
--data-dir并传递给每个命令。使用宿主包根目录下的.yosegi,这也是默认值;下面的示例仍会写出它,因为默认值跟随您的 cwd,从其他目录运行的命令将找不到您构建的内容。 - 是否有注册表,并且是最新的? 首先运行
yosegi registry status --data-dir .yosegi— 它会根据自己记录的输入重新计算注册表版本,并报告source: current或source: stale以及精确的重建命令,而不需要您通过component list的标题来目测。显示“Registry not found”表示首次运行。其 Storybook 索引部分是尽力而为的,可能报告索引不可达,而源判定仍然有效 — 将该部分理解为“无法验证”,而非“过时”或“最新”,不要让不可达的开发服务器阻止您信任current的源判定。只要status报告为stale、或打印了REGISTRY_VERSION_MISMATCH、或status报告source: unknown(因为注册表早于记录输入built: not recorded) — 那里没有可重新计算的内容,因此年龄是您唯一的信号。还要在命令警告注册表是由与您当前运行的不同 Yosegi 版本构建时重建:较旧的 Yosegi 无法输出当前版本可以输出的字段(例如函数 prop 的调用签名),没有其他信号能显示这一差距 — 版本字符串作为内容哈希保持不变,因此从未更改的宿主重建的注册表会保留相同的版本字符串。注册表是一个快照,过时的比没有更糟。
yosegi registry build \
--source "app/components/**/*.tsx" \
--tsconfig <host>/tsconfig.json \
--index http://localhost:6006/index.json \
--storybook-url http://localhost:6006 \
--report tmp/registry-report.json \
--data-dir .yosegi
上面的 6006 是 Storybook 自身的默认端口,仅在此使用,因为这是首次构建且尚未告知您其他信息 — 它不一定是宿主的端口。在运行此命令前,检查宿主 package.json 中的 Storybook 脚本(或询问)以获取实际运行的端口。首次构建后的每次重建有比猜测更好的来源:component list 的标题会打印一行 rebuild:,其中包含注册表上次构建时使用的精确 --storybook-url(references/cli.md)。
标志和默认值在 references/cli.md 中。--index 是唯一有前提条件的部分:开发服务器 URL 意味着宿主的 Storybook 必须已在运行。没有它您会失去 Story 派生类别和 recommended 标记,其他不变。
阅读其打印的统计信息:
files: 0→ glob 未匹配任何内容。命令仍然成功并写入仅包含三个合成原语的注册表,因此不要将“Wrote 3 components”视为成功。files为正数但componentCandidates: 0,或anyShapedProps较高但withNodeSlots: 0→ 构建虽然成功但降级了。第一种表示 glob 未覆盖任何 React 组件;第二种表示@types/react未解析,因此每个ReactNodeprop 都扁平化为json,未检测到插槽。两种情况下的Warning:行都会指明修复方法 — 解决后重建再继续。propsUnreadable相对于extractedComponents较高 → tsconfig 可能错误。立即解决(references/cli.md,registry metadata);处于该状态的组件稍后会拒绝完全真实的 props。
步骤 2. 查找您打算使用的每个组件
如果页面/路由生成器的 Example 模板是与您构建的屏幕最接近的匹配,请优先阅读它,或与下面的命令一起阅读 — 它决定了骨架和组合习惯用语,注册表然后确认模板留下的确切 props。
yosegi component list --category app/components/ui --data-dir .yosegi
yosegi component list --query card --data-dir .yosegi
yosegi component inspect "app/components/ui/button#Button" --data-dir .yosegi
注册表可能包含数百个条目,因此使用 --category / --query 缩小范围,从摘要中获取方向,然后 inspect 您将要编写的每个组件。**inspect 是权威的,而您的框架知识不是。**阅读 references/registry.md 以了解如何解读其输出 — 枚举选项、不透明 prop 下的 shape: 和 signature: 行、宿主实际编写的导入说明符以及未文档化 prop 的含义。
优先选择标记为 recommended 的组件;它们有自己的 Story。
然后收集宿主的约定。Story 会放入宿主仓库并作为宿主代码进行审查;跳过这一步会产生一个虽然工作但仍会被打回的 UI。
- 查找数据契约(如果已存在) — OpenAPI/JSON Schema 定义、GraphQL schema、手写的 API 响应 TypeScript 类型,无论宿主数据层使用何种形式。在编造 mock 数据之前阅读它。它决定的不仅仅是组件 props:哪些字段是必需的、可为 null 的(以及 null 对于特定字段的含义 — 例如 null limit 可能意味着“无限制”,而非“未知”)、哪些字段互斥,以及同样有用的是,没有什么(契约中没有排序或搜索字段意味着屏幕也不应发明这些)。这将 mock 从观感提案转变为反映真实数据形状的内容。其位置因宿主而异(
openapi/schemas/、.graphql文件、生成的types.ts);找到该域存在的那个,并遵循它而非看似合理的猜测。 - 搜索宿主的路由/页面目录(
app/routes/或提供页面的任何位置),查找同一域中现有的屏幕。在成熟产品中通常存在一个,找到它可能完全重新定义任务 — 例如,现有的bookings.calendar._index路由可能将“制作一个预订屏幕”转变为“针对现有预订流程提出一个横切列表”。这决定了您被要求制作的 mock 是否甚至是要构建的正确内容,因此在下面的检查点之前执行此操作,并让它塑造您在那里提出的问题。 - 在构建任何内容之前运行
git status并查看输出目录。 早先未提交的同一屏幕尝试可能已经存在于工作树中 — 在上下文重置和重新发出请求后很常见。与其创建重复文件,不如与之协调。要重做已存在的文件 — 宿主生成器在目标路径已存在时会拒绝,或者早先的尝试需要替换 — 将其移开(重命名)而非删除;您可能无权删除,而重命名无论如何都能清除路径。 - 检查宿主是否有新页面或屏幕的生成器 — plop/hygen 生成器、
bun add-route风格的脚本,通常记录在宿主的AGENTS.md或package.json脚本中。对于有此生成器的宿主,从头手写新屏幕文件是错误的:生成器克隆了授权的模板并强制执行文件和命名约定,因此请运行它而不是自行创建文件。这也决定了步骤 3 中内联树 Story 与页面模块加薄包装 Story 之间的选择 — 有这样生成器的宿主几乎总是希望后者。 - 从仓库根目录向下到输出目录的每一级阅读
AGENTS.md/CLAUDE.md,并遵循它们指向的任何文档。 - 阅读输出位置附近的一两个现有 Stories。组合多个组件的综合示例是值得模仿页面骨架、间距和组件选择的。**对于组合习惯用语 — 组件实际如何组装,而不仅仅接受哪些 props:驱动 DataGrid 的网格库 hook 调用、宿主特定的
meta(如{ stickyHeader: true })、组件期望的 wrapper — 现有 Story 是真实来源,注册表不是。**注册表不知道如何将组件组合成布局。
步骤 3. 编写 JSON 定义
现在您有注册表组件、宿主 Stories 以及(最好)宿主页面生成器的模板。在屏幕完全组装完成之前,您不会编写任何 React 代码。