Storybook 跨框架默认导出实战:用 Meta 与 component 字段驱动组件故事(CSF 3 与 CSF Next)
默认导出(default export)是 Storybook 组件故事格式(Component Story Format,即 CSF)的心脏:它声明"这份故事文件为哪个组件编写",从而驱动故事列表、自动标题、Docs 与各类 addon 的元数据。这篇指南以官方代码片段为骨架,逐一给出 Angular、React、Vue、Svelte、Solid、Preact、Ember、Web Components 各框架下带 component 字段的默认导出写法,并深入源码解释自动标题的推导规则,帮助你写出可被 Storybook 正确索引、结构清晰且类型安全的故事文件。读完你将掌握如何仅凭"组件即元数据"的最佳实践,就能让故事自动获得稳定标题与完整功能支持。
默认导出在 CSF 中的定位
CSF 是一种基于 ES6 模块的标准,易于编写且可在工具间移植。其关键组成有两个:
- meta(默认导出):描述组件及整个故事文件的公共元信息;
- 具名导出:描述一个个具体的故事(story)。
默认导出中的元数据控制 Storybook 如何在侧边栏中列出你的故事,并为各类 addon 提供必要信息。官方文档在 docs/writing-stories/index.mdx 中如此归纳:"The default export metadata controls how Storybook lists your stories and provides information used by addons."
// Button.stories.js|ts —— 概念示意
export default {
component: Button,
};
其中 component 字段是默认导出里最核心的配置之一:它指向故事要渲染的组件,也是本篇文章要展开的所有代码示例的公共主题。
只写 component:自动标题让 meta 保持极简
在绝大多数框架与写法中,默认导出只需要一个 component 字段即可。Storybook 会从文件路径与组件信息静态推导故事的标题(title),因此无需手工编写 title。
这一点在文档中有明确约束,docs/writing-stories/index.mdx 的提示框指出:
从 Storybook 7.0 开始,故事标题作为构建过程的一部分被静态分析。默认导出必须包含一个可以被静态读取的
title属性,或者包含一个可由其推导出自动标题的component属性。使用id属性自定义故事 URL 也必须可静态读取。
也就是说,component 是"静态可分析性"要求下的合规写法:你给出组件引用,Storybook 根据文件在项目中的位置自动计算层级化标题,例如位于 src/components/Button/Button.stories.js 的文件会得到标题 components/Button。
各框架默认导出写法速查
下面的代码片段摘自仓库中的 button-story-default-export-with-component.md,该片段被 docs/writing-stories/index.mdx 与 docs/essentials/controls.mdx 等文档直接引用。同一概念在不同渲染器(renderer)下,写法略有差异,核心都归结为"把组件放进默认导出"。
React:CSF 3 推荐 satisfies Meta
// Button.stories.js|jsx(CSF 3)
import { Button } from './Button';
export default {
component: Button,
};
TypeScript 场景下,官方注释建议把框架名替换为实际使用项(react-vite、nextjs、nextjs-vite 等):
// Button.stories.ts|tsx(CSF 3)
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
satisfies Meta<typeof Button> 让对象字面量在保留其推断类型的同时接受 Meta 类型的校验:component 的 props 类型会被严格检查,又不会被拓宽成泛化的 Meta,从而让 args、Controls 等功能的类型提示精确到组件 props。
Angular:基于类的组件声明
// Button.stories.ts(CSF 3)
import type { Meta } from '@storybook/angular';
import { Button } from './button.component';
const meta: Meta<Button> = {
component: Button,
};
export default meta;
Vue 3:SFC 组件 + satisfies Meta
// Button.stories.js(CSF 3)
import Button from './Button.vue';
export default {
component: Button,
};
// Button.stories.ts(CSF 3)
import type { Meta } from '@storybook/vue3-vite';
import Button from './Button.vue';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
Svelte:Svelte CSF 与标准 CSF 两种风格
Svelte 渲染器同时支持社区主导的 Svelte CSF 与标准 CSF。前者用 defineMeta 描述组件,用 Story 组件描述故事;后者沿用本文介绍的默认导出机制。二者的目标一致:声明"故事属于哪个组件"。
<!-- Button.stories.svelte(Svelte CSF) -->
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Button from './Button.svelte';
const { Story } = defineMeta({
component: Button,
});
</script>
// Button.stories.js(CSF 3)
import Button from './Button.svelte';
export default {
component: Button,
};
// Button.stories.ts(CSF 3)
// Replace your-framework with svelte-vite or sveltekit
import type { Meta } from '@storybook/your-framework';
import Button from './Button.svelte';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
Svelte CSF 的另一重要差异:它使用原生模板语法把 children 放在 <Story> 开闭标签之间(作为 children snippet prop 传给组件),而非通过 args,详见 docs/writing-stories/index.mdx。
Preact
// Button.stories.js|jsx
import { Button } from './Button';
export default {
component: Button,
};
Solid
// Button.stories.js|jsx
import { Button } from './Button';
export default {
component: Button,
};
// Button.stories.ts|tsx
import type { Meta } from 'storybook-solidjs-vite';
import { Button } from './Button';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
Ember:组件以字符串标识
// Button.stories.js
export default {
component: 'button',
};
Web Components:显式 title + 自定义元素名
Web Components 渲染器同样把组件注册为字符串(自定义元素标签名),代码片段中同时给出了 title 显式声明的形式:
// Button.stories.js(CSF 3)
export default {
component: 'demo-button',
};
// Button.stories.ts(CSF 3)
import type { Meta } from '@storybook/web-components-vite';
const meta: Meta = {
title: 'Button',
component: 'demo-button',
};
export default meta;
注意区别:Angular、React、Vue、Svelte、Solid、Preact 等框架把组件构造函数/类/对象作为 component,而 Ember 与 Web Components 由于组件模型不同,component 字段填写的是组件标识字符串(如 'button'、'demo-button')。
CSF Next(实验性):用 preview.meta 取代默认导出
仓库文档还收录了一种实验性的下一代写法 CSF Next(文档中以 🧪 标记,源自 RFC 讨论)。它与 CSF 3 最大的差别在于:meta 对象不再作为 default export 导出,而是通过由 docs/api/csf/csf-next.mdx 中的 definePreview 工厂返回的 preview 调用 preview.meta(...) 创建。
其工厂链设计为 definePreview → preview.meta → meta.story,每一步都携带完整类型信息。下面是各框架对应的 meta 定义方式:
// Button.stories.ts(Angular · CSF Next)
import preview from '../.storybook/preview';
import { Button } from './button.component';
const meta = preview.meta({
component: Button,
});
// Button.stories.tsx(React · CSF Next)
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
});
// Button.stories.ts(Vue · CSF Next)
import preview from '../.storybook/preview';
import Button from './Button.vue';
const meta = preview.meta({
component: Button,
});
// Button.stories.ts(Web Components · CSF Next)
import preview from '../.storybook/preview';
const meta = preview.meta({
title: 'Button',
component: 'demo-button',
});
需要注意,CSF Next 目前属于实验性 API,使用时需关注对应 feature flag 与发布说明;标准 CSF 3 仍是仓库中所有官方文档示例(包括 Controls、args 等章节)的主力写法,因此 export default meta 依旧是最稳妥的默认选择。
源码级解析:component 是如何"变成"标题的
只写一个 component 字段就能工作,背后是 Storybook 核心的**自动标题(auto-title)**机制。其实现位于 code/core/src/shared/story-index/autoTitle.ts,核心函数是 userOrAutoTitleFromSpecifier 与 userOrAutoTitle。
推导规则可概括如下(对照 autoTitle.test.ts 中的用例):
- 故事文件必须命中 stories 匹配器(如
./src目录下的**/*.stories.*),否则返回空; - 若用户未提供显式
title,则去掉directory前缀后,把剩余路径按/拆分作为标题层级; sanitize处理冗余:移除尾部的.stories.ts之类的扩展;如果末段与倒数第二段同名(如button/button.stories.js),会折叠重复段,得到to/button;index.stories.js也会被折叠为上一级目录名;- 支持
titlePrefix(在directory之外统一加前缀,例如atoms/前缀会拼成atoms/to/file); - 路径统一用
slash转换为 Unix 风格,兼容 Windows 反斜杠。
例如测试用例 './path/to/button/button.stories.js' 得到标题 to/button,而 './path/to-my/file.stories.js' 得到 to-my/file。此外,从 Storybook 7.0 起该逻辑作为构建期静态分析的一部分运行,所以 component/title/id 都必须是字面量可静态读取的,不能是运行时动态拼接的值。
这个由自动标题生成的故事 ID 最终在 code/core/src/common/utils/get-story-id.ts 中结合 toId 转成稳定的 storyId,并用于预览端的索引;在预览运行时,Storybook 通过 code/core/src/preview-api/modules/store/StoryStore.ts 接收这些可能在服务端(core-server)由自动标题生成的标题。从源码结构可以推断:文件路径 → 自动标题 → storyId 是一条端到端的数据链,而 component 字段正是这条链上"由组件推导标题"的锚点。
component 字段的其他用途:Docs、Controls 与 addon 元数据
component 不止用于生成标题,它还承担以下职责:
- Docs / Autodocs:文档块(
<Meta of={...}>等)需要把组件与 CSF 文件关联起来。在 code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts 中可以看到,csfFile.meta.component被用于在多个 CSF 文件间建立"组件即来源"的关联判断,从而在文档页定位该组件的 stories 与 meta。 - Controls 等交互式 addon:docs/essentials/controls.mdx 多次复用的正是本文的默认导出片段。只有 meta 正确指向组件,Storybook 才能读取组件 props/入参类型,为 args 自动生成对应的控制面板(输入框、下拉、开关等)。
- Sidebar 层级组织:meta 的标题层级决定了侧边栏的分组与顺序,默认导出是这一切的元数据入口。
常见问题与最佳实践
结合片段与源码,可提炼出以下几条可直接照做的结论:
- 默认先写
export default { component }:除非组件名无法自动推导出理想层级,否则优先依赖自动标题,保持文件精简、避免 title 手写不一致导致的拼写漂移。 - TypeScript 项目用
satisfies Meta<typeof Component>(Angular 用Meta<Button>泛型),把 props 类型信息无缝传递给 args 与 Controls,同时保留字面量类型。 - 需要特殊分组时再显式
title:例如 Web Components 示例中title: 'Button'与字符串component并存;Ember、Web Components 记得用组件标识字符串而非组件类。 - 遵守静态可分析约束:不要用变量、函数返回值去拼
title/component/id,否则 7.0+ 的构建期静态分析将无法为其生成稳定 ID。 - Svelte 用户:若使用 Svelte CSF 的
defineMeta({ component }),其语义与标准 CSF 默认导出等价;两者可依据团队偏好选择。 - 尝鲜 CSF Next:确认你的版本启用实验语法后再使用
preview.meta,正式项目仍以 CSF 3 的 default export 为主。
小结
默认导出(meta)是 CSF 文件的结构骨架,而 component 字段则是把故事绑定到真实组件的最小必要信息。无论是 Angular/React/Vue/Solid 的组件引用式写法,还是 Ember/Web Components 的字符串标识式写法,亦或 Svelte CSF 的 defineMeta 与实验性 CSF Next 的 preview.meta,其本质一致:用"组件即元数据"换取自动标题、自动文档与 addon 全链路支持。理解 auto-title 在 code/core/src/shared/story-index/autoTitle.ts 中的路径折叠规则,你就能预测每个故事文件的最终标题,写出更规范、更易维护的故事代码。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00