Storybook 组合架构实践仅用 MDX 桥接页面构建一个零本地 Stories 的聚合 Storybook【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook场景定位在采用 Storybook Composition 的多 Storybook 项目中你是否需要一个“本身不写任何组件 Stories、只负责把其他 Storybook 聚合起来当作导航壳”的独立 Storybook本文基于官方 FAQ 中的“Can I have a Storybook with no local stories?”一问及其配套配置片段完整讲解其成立前提、Introduction.mdx引导页的写法以及.storybook/main.ts|js中stories与refs两个核心配置字段的组合用法并附各主流框架React、Vue、Angular、Web Components的 TypeScript / JavaScript 双版本配置示例。一个反直觉的硬约束Storybook 必须有“本地内容”在动手配置之前先厘清官方文档docs/faq.mdx明确指出的一个约束Storybook 必须至少有一个本地定义的 story 或 docs page 才能正常工作。这里的“本地local”是一个有精确技术含义的词它指在项目自己的.storybook/main.js|ts配置中、通过stories字段被引用到的.stories.*或.mdx文件。换句话说判定“本地内容”的依据不是磁盘上是否存在这些文件而是它们是否被你的main配置显式纳入加载范围。这一点可以从stories字段的定义得到印证它是 main 配置中的一个必填Required字段类型为字符串 glob 数组或返回该数组的异步函数作用就是“指示 Storybook 从指定位置加载 stories”详见 main-config-stories.mdx// 类型签名节选自官方 API 文档 type Stories (string | StoriesSpecifier)[] | async (list: (string | StoriesSpecifier)[]) (string | StoriesSpecifier)[];当 main 配置里没有任何条目能匹配到实际文件时Storybook 会因缺少可渲染的本地条目而无法正常启动或浏览——这正是在聚合场景下必须引入至少一个本地.mdx页面的根本原因。聚合场景与“桥接壳”Storybook 的典型诉求在 Storybook Compositionstorybook-composition.mdx架构下团队通常为每个独立应用或设计系统维护各自的 Storybook再通过一个总控 Storybook 将它们统一聚合形成单一入口供演示、文档与导航使用。此时会出现一个特殊需求这个总控 Storybook自身不想承载任何组件 stories它只扮演“胶水”角色。但上文的前置约束阻止了“stories 数组为空”的直白做法因此官方给出的解法非常优雅创建一个仅含基础标题与描述的.mdx文档页例如Introduction.mdx作为本项目唯一的本地内容让 Storybook 具备启动与浏览的锚点在main配置中把stories指向这个唯一的.mdx文件同时用refs声明所有需要聚合的远端 Storybook。这一配置片段即 docs/_snippets/main-config-stories-only-mdx.md 所记录的内容也是本文的核心骨架。第一步编写 Introduction.mdx 本地引导页MDX 页面需要 addon-docsStorybook 的文档插件支持。一个最小可用的Introduction.mdx形如下面这个摘自官方 FAQ 的示例# Welcome Some description here该文件在项目中的推荐摆放位置是仓库根目录与.storybook同级。它的作用有两个层面运行时层面成为stories字段唯一匹配到的文件满足“至少一个本地条目”的启动前提体验层面作为整个聚合 Storybook 的“首页 / 入口页”承载项目级介绍文字让访问者先看到整体说明而非直接坠入某个子 Storybook。第二步在 main 配置中组合 stories 与 refs接下来在.storybook/main.ts|js中完成关键配置。官方片段提供了 CSF 3 与 CSF NextdefineMain两种写法覆盖 common / react / vue3 / angular / web-components 五类 renderer以下完整整理。CSF 3 写法通用适用于各框架TypeScript.storybook/main.ts// Replace your-framework with the framework you are using, e.g., nextjs-vite, vue3-vite, angular, sveltekit, etc. import type { Preview } from storybook/your-framework; const preview: Preview { // ... // define at least one local story/page here stories: [../Introduction.mdx], // define composed Storybooks here refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, }; export default preview;JavaScript.storybook/main.jsexport default { // ... // define at least one local story/page here stories: [../Introduction.mdx], // define composed Storybooks here refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, };CSF Next 写法defineMain 实验性CSF Next 是文档中以“CSF Next ”标签标注的新一代配置入口其形态是从框架的/node子路径导出defineMain工厂函数把配置对象包进类型安全的调用中并直接在根导出。官方片段给出了 React、Vue3、Angular、Web Components 四种 renderer 的实现差异仅在storybook/.../node的导入来源。React.storybook/main.ts/.storybook/main.js// Replace your-framework with the framework you are using, e.g., nextjs, nextjs-vite, react-vite, etc. import { defineMain } from storybook/your-framework/node; const config defineMain({ // ... // define at least one local story/page here stories: [../Introduction.mdx], // define composed Storybooks here refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, }); export default config;// Replace your-framework with the framework you are using, e.g., nextjs, nextjs-vite, react-vite, etc. import { defineMain } from storybook/your-framework/node; const config defineMain({ // ... stories: [../Introduction.mdx], refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, }); export default config;Vue.storybook/main.tsJavaScript 版本仅去掉类型注解、其余结构相同import { defineMain } from storybook/vue3-vite/node; const config defineMain({ // ... stories: [../Introduction.mdx], refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, }); export default config;Angular.storybook/main.tsimport { defineMain } from storybook/angular/node; const config defineMain({ // ... stories: [../Introduction.mdx], refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, }); export default config;Web Components.storybook/main.tsimport { defineMain } from storybook/web-components-vite/node; const config defineMain({ // ... stories: [../Introduction.mdx], refs: { firstProject: { title: First, url: some-url }, secondProject: { title: Second, url: other-url }, }, }); export default config;上述各框架的.js变体与.ts变体在结构上完全一致只需去掉defineMain结果的显式配置变量类型标注、直接导出defineMain({...})即可官方片段中保留.js变体是为了在 CSF 3 与 CSF Next 并存过渡期内维持两类配置的完整示例。如果你使用 SvelteKit、Preact、HTML 等框架defineMain同样可以从其对应的storybook/框架/node路径导入——配置对象的形状不变。逐项解析refs 与 stories 在本配置中的语义stories如何只匹配 MDXstories: [../Introduction.mdx]是一个以.storybook目录为参照、相对项目根的 glob。对比默认脚手架配置见 main-config-stories.md中的常见写法stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)],默认配置同时纳入了src下所有*.mdx与*.stories.*而“零本地 stories”配置刻意把范围收缩到单一.mdx文件从而既不加载任何组件 story又能保留文档页。glob 语法遵循 picomatch且官方提示部分 addon 可能假定 Storybook 的默认命名约定改动命名时需留意兼容性main-config-stories.mdx。另外值得注意的是StoriesSpecifier对象形式也适用于此场景——例如{ directory: docs, files: Introduction.mdx }这类结构化写法其字段含义如下引自 main-config-stories.mdx字段必填类型默认值说明directory是string—开始查找 story 文件的目录相对项目根files否string**/*.(mdx\|stories.(js\|jsx\|mjs\|ts\|tsx))相对directory的 glob匹配要加载的文件名titlePrefix否string自动生成标题CSF 3.0 auto-titles时的前缀refs如何挂载远端 Storybookrefs字段专门用于配置 Storybook Composition类型为以字符串为键的对象映射每个值可以是对象字面量、返回对象字面量的函数或{ disable: boolean }见 main-config-refs.mdx{ [key: string]: | { title: string; url: string; expanded?: boolean, sourceUrl?: string } | (config: { title: string; url: string; expanded?: boolean, sourceUrl: string }) { title: string; url: string; expanded?: boolean, sourceUrl?: string } | { disable: boolean } }以片段中的firstProject/secondProject为例它给每个聚合源声明了标题与地址。实际部署中url通常指向各 Storybook 的线上或 CI托管地址例如使用 Chromatic 发布的 URL若想控制侧边栏中该 ref 默认是否展开、以及“查看源码”跳转目标可补充expanded可选默认true与sourceUrl可选字段参见 main-config-refs.md 的完整示例refs: { design-system: { title: Storybook Design System, url: https://master--xxxx.chromatic.com/, expanded: false, // Optional, true by default sourceUrl: https://github.com/your-org/your-repo, // Optional }, },更进一步的能力还包括函数形式refs[key]可写为函数依据运行时配置动态计算 ref 参数按需禁用某些依赖包会自动把它的 Storybook 聚合进你的项目Package Composition见 package-composition.mdx若不想引入可将该包名对应的值设为{ disable: true }键名的显示意义refs 对象的键如firstProject会成为侧边栏中对应聚合分组的标识前缀。从源码结构看聚合态的展示逻辑聚合后的远端内容在 UI 层面由独立的 ref 块承载。在 code/core/src/manager/components/sidebar/RefBlocks.tsx 中可以看到这类区块对空状态与异常状态的处理文案当某 ref 加载不到条目时展示“No stories found”该文件约第 178 行附近当整个被聚合 Storybook 为空时展示“This composed Storybook is empty. Perhaps no stories match your selected filters.”约第 229 行附近。这些 UI 分支从侧面印证了官方 FAQ 对“本地/远端”边界的强调本地stories加载失败与 ref 远端无内容是两条独立的处理路径——本地侧必须至少命中一个文件本方案中的Introduction.mdx远端侧则可容忍为空或由过滤条件筛空。侧边栏状态指示器对 Composition 的运行时状态感知如加载中 / 就绪 / 出错同样集中在 sidebar 组件中例如 RefIndicator.tsx 即负责渲染 ref 连接状态。实战注意事项与边界把该配置落地到真实项目时还需注意以下几点addon-docs 是必要前提MDX 文档页依赖 addon-docs现为 Essentials 内置。若你手动精简了 addon 列表需确保storybook/addon-docs已注册否则.mdx无法作为文档页解析。地址与标题请勿照抄示例url: some-url与title: First是占位符务必替换为真实托管地址与有语义的展示名。.js/.ts二选一main.js与main.ts同时存在会互相冲突请只保留其一文中同时给出两者仅为覆盖不同技术栈读者。远程聚合的 addon 能力受限官方 FAQ 明确指出当前版本下你在被聚合的远端 Storybook 中使用的 addon 在聚合视图中不会生效——聚合壳的价值在于统一导航与文档入口而非在壳内操作远端交互工具。保持唯一本地条目的纯粹性stories一旦写成[../Introduction.mdx]该项目的任何.stories.*文件都不会被加载。若后续发现某个本地 story 意外出现请检查是否有其他 glob如包依赖通过 Package Composition 注入被隐式合并进加载范围。静态分析带来的配置约束Storybook 现在会对配置文件做静态分析以提升性能官方建议避免在main中使用过于动态的自定义加载逻辑main-config-stories.mdx本方案这种“固定数组”写法正好处于最利于静态分析的形态。小结“零本地 Stories 的 Storybook”并非一个缺陷态而是 Storybook Composition 聚合架构中的标准组成部分。它的配置要点可以浓缩为两句话用stories只指向一个Introduction.mdx满足“必须有本地条目”的硬性前提用refs声明全部待聚合的远端 Storybook完成导航壳的搭建。无论你使用 CSF 3 的裸对象导出还是处于 CSF NextdefineMain过渡期配置骨架都保持一致唯一需要按框架切换的是类型导入来源与/node入口路径。按官方 FAQdocs/faq.mdx与本文所引的 配置片段、stories 字段参考、refs 字段参考 组合操作即可在数分钟内产出可运行的聚合壳项目。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
