Storybook ESLint 规则 `csf-component` 深入指南:为 CSF Meta 补全 component 属性
导读
本指南围绕 Storybook 官方 ESLint 插件(eslint-plugin-storybook)中的 storybook/csf-component 规则展开。该规则要求你在每个 CSF(Component Story Format)文件的默认导出 meta 中声明 component 属性。本文将说明规则为什么存在、会报告哪些代码模式、与 csf / csf-strict 配置的关系、何时应当关闭它,并结合插件源码剖析其真实检查逻辑与解析边界,帮助你在自己的 Storybook 项目中正确地启用和运用这条规则。
规则概览
csf-component 是 eslint-plugin-storybook 提供的一条静态检查规则,其核心理念是:每一个 CSF 文件默认导出的 meta 对象都应该显式声明 component 属性,把该文件组织下的所有 story 与某一个具体组件明确绑定。
从插件的规则注册表可以确认该规则的默认行为(src/rules/csf-component.ts):
- 规则类型:
suggestion,默认警告级别severity: 'warn'; - 错误信息:
Missing component property.(内部messageId为missingComponentProperty); - 无任何可配置选项(
schema: []),启用即按默认行为工作; - 所属分类:
csf(CategoryId.CSF)。
出现在哪些推荐配置中
该规则被下列四个预置配置自动包含:
| 配置名称 | 面向 ESLint 版本/风格 |
|---|---|
csf |
ESLint v8 及更早的 .eslintrc.* 风格 |
flat/csf |
ESLint v9 的 flat config 风格 |
csf-strict |
.eslintrc.* 风格下的 CSF 严格模式 |
flat/csf-strict |
flat config 风格下的 CSF 严格模式 |
也就是说,只要你在项目中启用了 plugin:storybook/csf(或 flat/csf、csf-strict、flat/csf-strict),这条规则就会自动生效。可以对比参考 README 中的规则总表,例如 storybook/default-exports、storybook/hierarchy-separator、storybook/no-redundant-story-name 同样同时出现在 csf 与 csf-strict 中,而 csf-strict 还会额外启用 storybook/no-stories-of、storybook/no-title-property-in-meta 等更严格的约束。
提示:
storybook/csf-component并不包含在默认的plugin:storybook/recommended配置中,需要显式开启csf/csf-strict系列配置才会被加载。
为什么应当设置 component 属性
在 CSF 语法中,component 属性虽然是可选的,但显式设置它能解锁 Storybook 的一系列核心能力,这也是规则存在的根本原因:
-
自动生成 Props 表格文档(prop table):在大多数框架下,Storybook 会根据
component指向的组件,通过 docgen 机制提取其属性、类型、默认值、描述等,在 Docs 页面上自动渲染成参数表格。这依赖工具链对该组件的类型信息或源码注释进行分析,但起点必须是meta中声明的component。 -
自动生成 Controls(动态编辑面板):基于上述自动推断出的
argTypes,Storybook 的 Controls 插件才能为每个参数渲染合适的编辑控件(如文本输入、颜色选择器、下拉框等),让你在 UI 上实时改参、即时预览。 -
CSF3 下无需 render 函数的默认渲染:在 CSF3 中,一旦声明了
component,即使某条 story 没有显式定义render/args之外的渲染逻辑,Storybook 也能用合理的默认方式渲染该组件;而缺少component时,通常必须为 story 显式提供render函数,否则无法得知要渲染什么。 -
可读性与可维护性:把「这个文件的故事属于哪个组件」写到默认导出里,使组件与故事文件的对应关系一目了然,也方便工具做导航分组与代码跳转。
从源码层面看,component 缺失时规则会基于 AST 直接报告(见下文「实现原理」),并不会去验证 component 的值是否是一个真实存在的组件——它只负责督促你养成声明该属性的习惯,真正"解码"组件类型、生成文档的工作由 Storybook 运行时与框架的 docgen 工具完成。
规则细节:正确与错误的示例
会被报告的“不正确”代码
缺少 component 的默认导出都会触发 Missing component property.:
export default {
title: 'Button',
};
// 即便使用 Meta<T> 类型标注也不能豁免
export default {
title: 'Button',
} as ComponentMeta<typeof Button>;
被判定为“正确”的代码
显式声明了 component 即可通过检查:
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
};
用上 TypeScript 的类型标注同样合法:
import type { Meta } from '@storybook/react';
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
} satisfies Meta<typeof Button>;
对应的测试用例可以从 src/rules/csf-component.test.ts 中看到:valid 一组收录了内联对象与 as ComponentMeta<typeof Button> 两种写法;invalid 一组则覆盖了纯内联、as 断言、以及把 meta 先赋给变量再默认导出(const meta = {...}; export default meta)三种缺失场景,均报告在 ExportDefaultDeclaration 节点上。
实现原理:规则是如何检查的
深入阅读 src/rules/csf-component.ts 可以看清它的完整工作流:
- 监听 AST 中的
ExportDefaultDeclaration节点; - 调用
getMetaObjectExpression(node, context)提取默认导出背后的meta对象表达式; - 在 meta 对象的
properties中查找键名为component的属性,跳过展开元素(spread element); - 若找不到,就在该节点上报告
missingComponentProperty。
其中几个实现细节非常关键:
meta 解析支持多种写法
getMetaObjectExpression(src/utils/index.ts)会依次处理这些情况,因此规则能够理解多样化的 meta 书写方式:
- 直接对象字面量:
export default { title: 'Button', component: Button }; - 标识符引用 + 变量声明解析:遇到
export default meta这种形式时,通过 scope 找到变量定义(const meta = ...)再取其初始化表达式,所以上面「间接导出」的写法同样会被检查; - TS 类型断言与 satisfies:遇到
as(TSAsExpression)或satisfies(TSSatisfiesExpression)包裹的表达式时,会先剥掉外层类型节点再取内部对象; - 最终只有解析结果是
ObjectExpression时才会继续,否则规则直接跳过(例如导出的是函数或其他非对象形式,就不适用本规则)。
Spread 元素会被忽略
检查 component 属性时使用了 !isSpreadElement(property) 的守卫(src/rules/csf-component.ts,其类型判断定义见 src/utils/ast.ts)。也就是说,如果 meta 是通过 ...baseMeta 展开合并而来的,即使展开的源头里其实含有 component,静态分析层面也不会把展开元素计入——这与 ESLint 不做跨文件/跨作用域数据流分析的定位一致。如果你确实使用了 spread 合并 meta 的写法,这条规则可能会误报,请参考下面的「何时不使用本规则」。
兼容 ESLint v8 与 v9
在解析 export default meta 这类标识符引用时,代码特意兼容了 ESLint v8 的 context.getScope() 与 v9 的 sourceCode.getScope(node) 两套 API(src/utils/index.ts),因此该规则在 .eslintrc.* 与 flat config 两种模式下都能正常工作。
启用方式与配置示例
通过 csf 配置启用(.eslintrc.*)
在项目根目录的 .eslintrc.* 中 extends 对应的配置即可,插件前缀 eslint-plugin- 可以省略:
{
"extends": ["plugin:storybook/csf"]
}
如果你希望同时得到更严格的 CSF 检查(例如禁止 storiesOf、禁止在 meta 中写 title),可以使用:
{
"extends": ["plugin:storybook/csf-strict"]
}
插件会自动把规则仅应用于 *.stories.*(推荐)或 *.story.* 文件,无需额外配置匹配。
通过 flat/csf 配置启用(eslint.config.js)
ESLint v9(或 v8.57+ 使用 flat config)下,在 eslint.config.js / eslint.config.mjs 中展开对应配置对象:
import storybook from 'eslint-plugin-storybook';
export default [
// ...其他通用 ruleset,例如 js.configs.recommended
...storybook.configs['flat/csf'],
];
对应严格模式则替换为:
...storybook.configs['flat/csf-strict'],
flat/csf-strict 的命名规则对象可以在 src/configs/flat/csf-strict.ts 中核对,插件的所有配置入口集中在 src/index.ts。
单独覆写规则强度
在 csf / csf-strict 已开启的前提下,你可以针对自己的 stories 文件把警告升级为错误或直接关闭。以 .eslintrc.* 为例:
{
"overrides": [
{
"files": ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)"],
"rules": {
"storybook/csf-component": "error",
"storybook/csf-component": "off"
}
}
]
}
flat config 下则在对应文件匹配块中写入 rules 即可,具体写法可参考 README 的覆写示例。
安装插件后,建议在
.eslintignore中追加!.storybook,让插件也能检查.storybook目录下的配置文件(例如校验 addon 名称拼写)。
当你想用 MDX 书写故事时需要注意
需要注意:eslint-plugin-storybook 本身不支持 MDX 文件(参见 README 的 MDX Support 一节)。因此 csf-component 只作用于基于 JS/TS 的 CSF 文件。如果你使用 .mdx 形式的 CSF(即 stories 与 docs 都写在 MDX 中),该规则不会生效,需要依赖 Docs 内文档块(如 <Story />)或 docgen 的其他约定来获得组件信息。
何时不使用本规则(When Not To Use It)
虽然 eslint-plugin-storybook 鼓励每个 CSF 文件都清晰地对应当前文件中的一个组件,但 Storybook 本身允许你按任何方式组织你的内容。以下几种情况你可能并不需要(甚至不应该)开启 csf-component:
- 一个 CSF 文件聚合多个组件或混编 docs:例如把若干个紧密相关的小组件、或纯文档型页面(docs-only)放在同一个文件中统一叙事;
- 存在无组件的抽象故事层:比如某些组合式 story 只是把子故事组织到一起,本身并不直接渲染一个独立组件;
- 大量依赖
render函数 + 手动argTypes的团队约定:如果团队所有 story 都显式提供了render并手写了完整argTypes,那么component带来的自动推断增益有限; - 使用 spread 合并 meta:如上一节所述,规则无法穿透
...spread静态识别component,此类代码会被误报为缺失。
面对这些组织方式,正确做法是关闭或降级这条规则(见上文覆写配置),而非强行改造自己的文件结构来迎合检查器。文档对此也给出了同样的取舍建议:组织方式不同时,本规则可能并不适用于你的项目。
关联阅读与延伸
围绕这条规则涉及的核心能力,值得继续探索的仓库文档与源码包括:
- eslint-plugin-storybook 的规则总表与全部配置:定位
csf-component与其他 CSF 规则的包含关系; - args 与 argTypes 的自动推断文档:理解
component属性如何驱动 docgen 生成类型驱动的控件与表格,这是csf-component规则价值的最直接体现; - src/rules/csf-component.ts:本规则的完整实现;
- src/rules/csf-component.test.ts:规则的可执行行为契约;
- src/utils/index.ts:
getMetaObjectExpression的 meta 解析逻辑,也是理解多个 CSF 规则共同底座的关键。
小结
storybook/csf-component 是一条轻量但收益明确的规则:它通过 AST 静态检查督促你在每个 CSF 文件的 meta 中声明 component,从而让 Storybook 的 prop 文档、动态 Controls 与 CSF3 默认渲染能力真正运转起来。它随 csf / csf-strict(含 flat 变体)配置自动启用,支持直接对象、变量引用、as/satisfies 类型标注等多种 meta 写法,但对 spread 展开与 MDX 文件无能为力——理解这些边界,能让你在享受自动化收益的同时,为多组件聚合、docs-only 等特殊组织方式做出合理的规则取舍。
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 StartedRust0626
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