Storybook Code Panel 按组件(Meta)与按 Story 细粒度启用全指南:parameters.docs.codePanel配置详解
本指南围绕 Storybook 的 Code Panel(代码面板)功能展开,重点讲解如何通过parameters.docs.codePanel参数在组件级(Meta)与单个 Story 级分别控制面板的显隐,覆盖 CSF 3、CSF Next(实验性)与 Svelte CSF 三种写法,以及 React、Vue 3、Angular、Web Components、Svelte 等框架的差异。读完本文,你将能精确到“某个文件开启、某个 Story 关闭”地管理代码面板,并理解该面板底层由哪些源码与通道事件驱动。
Code Panel 是什么:在画布中直接阅读 Story 的真实源码
Code Panel 是 Storybook Docs 提供的“故事源码预览面板”:当你在画布(Canvas)中查看某个 Story 时,切换到Code标签页即可看到该 Story 的源码,并且Story 中定义的 args 会被替换成其实际传入值,因此你看到的是与当前渲染结果严格一致的“可运行源码”,而非模板占位符。
按当前仓库文档 docs/writing-docs/code-panel.mdx 的说明,Code Panel 是Storysource 插件的替代方案(该插件在 Storybook 9 中被移除)。仓库中对应提供了自动化迁移修复addon-storysource-code-panel(见 code/lib/cli-storybook/src/automigrate/fixes/addon-storysource-code-panel.ts),负责移除@storybook/addon-storysource并在预览配置中写入parameters.docs.codePanel: true,我们将在后文展开。
从实现看,该面板是注册在 manager 端(UI)的一个 Addon Panel。仓库 code/addons/docs/src/manager.tsx 中通过addons.add注册了标题为Code的面板,并声明了两条关键规则:
disabled: (parameters) => !parameters?.docs?.codePanel—— 面板的“禁用”状态完全取决于当前渲染上下文合并后的parameters.docs.codePanel;match: ({ viewMode }) => viewMode === 'story'—— 面板仅在 story 视图(画布)中出现,而不出现在 docs 页面。
换言之,只要不显式设置codePanel: true,面板标签就不会出现;而开启后,默认面板始终对当前 Story 生效。
三层配置作用域与优先级:全局预览、组件(Meta)、单个 Story
docs.codePanel是一个布尔参数,理论上可以放在 Storybook 参数体系中的任何一层,实际使用中遵循 Storybook 的 parameters 合并规则:Story 渲染时会按preview → meta(组件)→ story的顺序合并各级parameters,下级对上级进行覆盖。因此你可以非常灵活地组合:
| 配置位置 | 配置方式 | 影响范围 |
|---|---|---|
全局.storybook/preview.* | parameters.docs.codePanel: true | 项目内所有 Story 都显示 Code Panel |
组件级meta | parameters.docs.codePanel: true / false | 该.stories文件内所有 Story 生效 |
Story 级parameters | parameters.docs.codePanel: true / false | 仅覆盖该 Story,粒度最细 |
官方推荐的默认做法是在.storybook/preview.*中全局开启(见 docs/_snippets/code-panel-enable-in-preview.md),这样所有 Story 默认都展示源码面板;如果个别组件或 Story 需要例外,再用后两层做局部覆盖。
实际常见用法是在 meta 层开启、在某个 Story 层单独关闭(或在全局开启后局部关闭)。这正是本文重点讲解的“Meta 与 Story”两级控制:
- meta 中
codePanel: true→ 该文件内每个 Story 都默认显示 Code 标签页; - 某个 Story 中
codePanel: false→ 仅该 Story 不显示面板,文件内其他 Story 不受影响。
在 Meta 与 Story 中配置的完整代码示例(多框架)
由于 Code Panel 属于 docs 参数体系,任何使用 Storybook Docs 的框架写法一致——把codePanel放进docs参数即可。下面按写法形态分组给出可直接复制运行的完整示例。
形态一:标准 CSF 3 + TypeScript(React 示例)
Button.stories.tsx:
import type { Meta, StoryObj } from '@storybook/react-vite'; import { Button } from './Button'; const meta = { component: Button, parameters: { docs: { // 👇 Enable Code panel for all stories in this file codePanel: true, }, }, } satisfies Meta<typeof Button>; export default meta; type Story = StoryObj<typeof meta>; // 👇 This story will display the Code panel export const Primary: Story = { args: { children: 'Button', }, }; export const Secondary: Story = { args: { children: 'Button', variant: 'secondary', }, parameters: { docs: { // 👇 Disable Code panel for this specific story codePanel: false, }, }, };要点说明:
codePanel必须嵌套在parameters.docs内,与docs.page、docs.canvas、docs.source同级;- 上面的
Primary没有写任何codePanel,继承 meta 的true,因此会显示 Code 面板; Secondary在自身parameters.docs中把codePanel覆盖为false,因此不显示面板。
形态二:CSF 3 在 Angular、Vue 3、Web Components 中的差异点
本文件对应的原始代码片段 docs/_snippets/code-panel-in-meta-and-story.md 按渲染器给出了 angular、react、svelte、vue、web-components 五个渲染器、每种含 TS/JS 与 CSF 3/CSF Next 的完整变体。除导入路径与类型书写略有差异外,结构与上面的 React 示例完全一致,差异点如下:
- Angular(TS):
import type { Meta, StoryObj } from '@storybook/angular';,组件为类组件Button from './button.component',写法同 CSF 3。 - Vue 3(TS):
import type { Meta, StoryObj } from '@storybook/vue3-vite';,组件为import Button from './Button.vue'。 - Web Components(TS):最特殊——
meta不再使用组件类,而是自定义元素标签名:
import type { Meta, StoryObj } from '@storybook/web-components-vite'; const meta: Meta = { component: 'demo-button', // 👈 自定义元素标签名,而非组件对象 parameters: { docs: { // 👇 Enable Code panel for all stories in this file codePanel: true, }, }, }; export default meta; type Story = StoryObj; // 👇 This story will display the Code panel export const Primary: Story = { args: { children: 'Button', }, }; export const Secondary: Story = { args: { children: 'Button', variant: 'secondary', }, parameters: { docs: { // 👇 Disable Code panel for this specific story codePanel: false, }, }, };- JS 变体:去掉
import type与satisfies Meta<typeof Button>类型标注,改用export default { component: Button, ... }的对象字面量形式,逻辑完全相同。
形态三:CSF Next(🧪 实验性)的preview.meta/preview.story写法
CSF Next 是仓库文档中标注为实验性(🧪)的新写法,它不再使用export default meta,而是从.storybook/preview导入preview对象,再调用preview.meta({ ... })与preview.story({ ... })构造。codePanel参数本身与 CSF 3 没有区别,例如 React TS 变体:
import preview from '../.storybook/preview'; import { Button } from './Button'; const meta = preview.meta({ component: Button, parameters: { docs: { // 👇 Enable Code panel for all stories in this file codePanel: true, }, }, }); // 👇 This story will display the Code panel export const Primary = meta.story({ args: { children: 'Button', }, }); export const Secondary = meta.story({ args: { children: 'Button', variant: 'secondary', }, parameters: { docs: { // 👇 Disable Code panel for this specific story codePanel: false, }, }, });CSF Next 同样覆盖 Angular(definePreview/preview.meta来自@storybook/angular)、Vue 3(@storybook/vue3-vite)、Web Components(@storybook/web-components-vite)与 React(@storybook/your-framework),TS/JS 差异同样只在于类型标注。
形态四:Svelte CSF(addon-svelte-csf)的.stories.svelte写法
Svelte 额外支持把 Story 写在.svelte单文件里,通过<script module>中的defineMeta定义 meta,用<Story>组件声明各 Story。注意此时Story 级覆盖通过<Story parameters={{ ... }}>属性传入:
<script module> import { defineMeta } from '@storybook/addon-svelte-csf'; import Button from './Button.svelte'; const { Story } = defineMeta({ component: Button, parameters: { docs: { // 👇 Enable Code panel for all stories in this file codePanel: true, }, }, }); </script> <Story name="Primary" args={{ children: 'Button', }} /> <Story name="Secondary" args={{ children: 'Button', variant: 'secondary', }} parameters={{ docs: { // 👇 Disable Code panel for this specific story codePanel: false, }, }} />若你使用普通 Svelte CSF 3 写法(.stories.ts文件),则与 React/TypeScript 的 CSF 3 形态一致,导入Meta、StoryObj时按注释把@storybook/your-framework替换为实际使用的svelte-vite或sveltekit即可。
面板内容由谁决定:源码级原理
理解“在哪里开启”之后,再理解“面板到底渲染什么”能让配置更可控。仓库 code/addons/docs/src/manager.tsx 的CodePanel组件揭示了内容来源,其取值优先级为:
parameter.source?.code—— 即docs.source.code参数中硬编码的源码,优先级最高;codeSnippet.source—— 渲染管线通过SNIPPET_RENDERED通道事件推送过来的、针对当前 Story 生成的代码片段;parameter.source?.originalSource—— 兜底使用参数的原始源码。
关于第 2 点,manager 端通过useChannel监听SNIPPET_RENDERED事件,并校验事件中的id必须等于currentStoryId,避免“上一次选中 Story 的异步片段生成完成后覆盖当前面板”的竞态问题;随后用主题感知的Source组件(支持暗色主题)把代码渲染进AddonPanel。
而参数类型的权威定义在 code/addons/docs/src/types.ts 的DocsParameters接口中:
export interface DocsParameters { docs?: { /** * Enable the Code panel. * * @see https://storybook.js.org/docs/writing-docs/code-panel */ codePanel?: boolean; // ... source?: Partial<SourceBlockParameters>; // ... }; }可以看到,codePanel是布尔可选值,未设置即视为关闭;它与docs.source、docs.canvas等参数同属 docs 命名空间,因此设置docs.codePanel时不要误写成parameters.codePanel。
用docs.source参数自定义 Code Panel 的内容
如 docs/writing-docs/code-panel.mdx 所述,Code Panel 渲染的正是 Source 文档块(Doc Block Source)使用的同一条代码片段,因此它复用 Source 的全部配置参数。也就是说:与其在 meta/Story 层反复开关codePanel,不如进一步用docs.source微调展示内容。仓库中的模板 Story code/addons/docs/template/stories/codePanel/index.stories.tsx 就演示了这三种典型用法:
export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], parameters: { chromatic: { disableSnapshot: true }, docs: { codePanel: true, // 整个文件开启 Code Panel }, }, }; /** 展示 Code panel 的默认 Story,同时强制画布中源码区可见 */ export const Default = { args: { label: 'e2eStoryDocsBefore' }, parameters: { docs: { canvas: { sourceState: 'shown', }, }, }, }; /** 用 docs.source.code 硬编码自定义源码 */ export const CustomCode = { args: { label: 'Custom code' }, parameters: { docs: { source: { code: '<button>Custom code</button>', }, }, }, }; /** Story 级关闭 Code Panel */ export const WithoutPanel = { args: { label: 'Without panel' }, parameters: { docs: { codePanel: false, }, }, };这个模板文件本身被仓库的端到端测试 code/e2e-internal/story-docs.spec.ts 复用:测试会打开标题为Code的标签页并断言面板文本内容,例如“在不发生导航的情况下修改 Story args 后,Code Panel 与 Autodocs 源码随之热更新”,从而验证了“Code 面板显示的源码会随 args 实时同步”这一行为。
从 Storysource 迁移到 Code Panel(自动化修复)
如果你此前使用@storybook/addon-storysource,升级到新版本后可通过 automigrate 一键迁移。仓库中 code/lib/cli-storybook/src/automigrate/fixes/addon-storysource-code-panel.ts 定义了 id 为addon-storysource-code-panel的修复项,其核心流程为:
- 检查项目 addons 中是否包含
@storybook/addon-storysource; - 命中后提示“将移除 @storybook/addon-storysource 并改用 Code Panel”;
- 从 addons 中移除该插件,并自动在预览配置文件里写入
parameters.docs.codePanel: true(通过previewConfig.setFieldValue(['parameters', 'docs', 'codePanel'], true))。
因此,旧版依赖 Storysource 显示源码的用户,在新版中应直接改用本文的codePanel参数——对绝大多数项目,全局在.storybook/preview.*中开启一次即可,之后仅在个别组件(meta)或个别 Story 中做精确的true/false覆盖。
最佳实践小结
- 默认全局开启:在
.storybook/preview.*(TS 版可参考 docs/_snippets/code-panel-enable-in-preview.md)中写docs: { codePanel: true },让所有 Story 获得一致的“可复制源码”体验; - 文件级批量控制放 meta:某些示例文件不希望暴露实现细节时,可在该
meta的parameters.docs中设codePanel: false,或对个别演示 Story 单独覆盖; - Story 级精细覆盖:在需要隐藏源码、展示“无代码”形态(例如截图基线、无障碍对比)的 Story 上设
codePanel: false,互不影响; - 结合
docs.source使用:面板与 Source 块共享渲染管线,自动生成的代码不理想时,可用source.code直接指定展示内容; - 注意参数路径:务必写全
parameters.docs.codePanel,且布尔值不要写成字符串;未设置时面板默认隐藏,这与 manager 端disabled: (parameters) => !parameters?.docs?.codePanel的实现完全对应。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考