Storybook 插件注册实战:如何在 main.js 中挂载 Preset 类插件(以 @storybook/addon-docs/preset 为例)
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
Storybook 的addons配置是扩展组件工作台能力(文档、测试、无障碍检测、主题切换等)的总入口,而其中又以Preset(预设)类插件最为灵活:它不仅能注入 UI 入口,还能叠加 Babel、Vite、Webpack 等构建配置。本篇以官方文档推荐的@storybook/addon-docs/preset为例,讲解在 .storybook/main.js 中注册 Preset 的标准写法,并结合当前仓库源码剖析其背后的解析与装载机制。读完本文,你将掌握 Preset 与普通插件的区别、managerEntries的作用,以及如何为自己的项目或自研插件正确选择注册方式。
核心示例:在 addons 数组中注册 Preset
在 Storybook 的项目配置文件.storybook/main.js(或main.ts)中,addons字段用于声明需要加载的插件与预设。官方文档给出的最小化示例非常直观——直接在数组中写入带/preset后缀的包名:
export default { addons: [ '@storybook/addon-docs/preset', // A preset registered here, in this case from the addon-docs addon. ], };这段配置的含义是:将@storybook/addon-docs插件中面向配置层的 preset 入口注册进 Storybook。addon-docs同时提供了 manager 端(UI)与 preview 端(渲染)能力,而通过@storybook/addon-docs/preset这种显式后缀,可以让 Storybook 一次性装载它预置的全部功能——包括 Docs 页面的 UI 注册、MDX/CSF 文档索引、以及构建阶段需要的转换与配置。
需要说明的是,addons数组支持三种合法写法,参见 main-config-addons.mdx 的类型定义(string | { name: string; options?: AddonOptions })[]:
| 写法 | 示例 | 适用场景 |
|---|---|---|
直接写包名(含/preset后缀) | '@storybook/addon-docs/preset' | 显式声明要加载某个 preset 入口 |
| 直接写包名(无后缀) | '@storybook/addon-a11y' | 交给 Storybook 自动探测入口,推荐用于普通插件 |
| 对象形式 | { name: '@storybook/addon-styling-webpack', options: { rules: [...] } } | 需要向插件传递额外配置参数 |
其中对象形式的完整示例可参考 main-config-addons.md:options会原样传递给该插件的 preset,例如addon-styling-webpack通过options.rules注入自定义的 CSS 处理链。如果项目使用 TypeScript 编写配置文件,可用StorybookConfig类型获得完整类型提示:
import type { StorybookConfig } from '@storybook/your-framework'; // 替换为实际使用的框架,如 react-vite、nextjs、vue3-vite const config: StorybookConfig = { addons: ['@storybook/addon-docs/preset'], }; export default config;Preset 与普通插件:一条 addons 声明背后的两条装载路径
为什么addon-docs需要单独暴露一个preset入口?这要从 Storybook 的插件分类说起。官方文档 writing-presets.mdx 指出:Preset 类插件允许开发者通过 API 组合各种配置选项与插件,将"安装后零配置生效"的体验打包给使用者。典型的 preset 会被拆分为两个文件,各司其职:
- 本地 preset(local preset):面向构建层,负责导出
webpackFinal、viteFinal、babelDefault等配置钩子,参见 storybook-addons-local-preset.md。它把插件的编译期需求(如 JSX 转换、样式处理)注入 Storybook 的构建管线。 - 根 preset(root preset):面向使用者,负责零配置注册插件整体能力——通过
previewAnnotations注入 preview 相关能力(如参数parameters),通过managerEntries注入 UI 相关能力(如侧边栏工具条、面板),参见 storybook-addons-root-preset.md:
export const previewAnnotations = [import.meta.resolve('./dist/preview')]; export const managerEntries = [import.meta.resolve('./dist/manager')]; export * from './dist/preset.js';可以看到,一个完整的 preset 出口会同时声明previewAnnotations(渲染端注入)与managerEntries(管理端 UI 注入),再把本地 preset 的构建钩子一并 re-export。这样使用者在addons里写一行包名,即可获得完整的插件体验——这正是@storybook/addon-docs/preset所遵循的打包模式。
而从配置解析的角度看,presets.ts 中的resolveAddonName函数注释明确列出了三类合法输入及其解析结果:
'@storybook/addon-docs/preset'→ 解析为{ type: 'presets', item },即一个显式 preset;'@storybook/addon-docs'(无后缀)→ 解析为{ type: 'presets', item: '@storybook/addon-docs/preset' },Storybook自动补全/preset后缀;{ name: '@storybook/addon-docs(/preset)?', options: {...} }→ 解析为带options的 preset。
源码中的注释与 writing-presets.mdx 的表述一致:"Storybook 会自动探测传入的值是 preset 还是普通插件,并据此加载"。resolveAddonName的具体做法是:依次尝试解析该包下的preset、manager、preview三个入口文件(resolveEntryFile('preset')、resolveEntryFile('manager')、resolveEntryFile('preview')),只要命中任意一个入口,就把该 addon 标记为virtual类型,并分别收集presets、managerEntries、previewAnnotations三份载荷(presets.ts)。这意味着你甚至可以在addons里直接引用一个只含manager.js入口的插件包——它会被识别为"仅注入管理端 UI"的轻量插件。
managerEntries:Preset 装载管理端 UI 的通道
在上述解析结果中,managerEntries指的是需要被打包进 Storybook 管理端(manager)bundle 的入口文件。管理端承载着搜索、导航、工具栏与各插件面板,是用户交互的界面层。preset 通过managerEntries把自己的面板代码注册进去,才能渲染出对应的 UI。
managerEntries还可以写成函数形式,用于在保留既有入口的基础上追加第三方插件入口。官方文档为此单独给出了示例 storybook-addons-root-preset-manager-entries.md:
export const managerEntries = (entry = []) => { return [...entry, import.meta.resolve('path-to-third-party-addon')]; };这种函数形态特别适合"preset 帮你加载第三方插件"的场景——例如你的 preset 依赖某个第三方 UI 插件,却无法直接控制其实现,此时通过managerEntries函数把该插件的 manager 入口追加进列表即可。参数entry是当前已收集到的入口数组,返回[...entry, 新入口]是约定俗成的写法,保证不覆盖其他 preset 贡献的入口。
从装载时机看,manager 入口的收集发生在 Storybook 启动构建管理端 bundle 时。builder-manager/index.ts 中的getConfig通过options.presets.apply('managerEntries', [])汇总所有 preset 声明的 manager 入口,再尝试解析项目配置目录下的本地./manager文件(支持.js、.mjs、.jsx、.ts、.mts、.tsx等扩展名),最终将二者合并作为管理端 bundle 的 entry points:
const [managerEntriesFromPresets, envs] = await Promise.all([ options.presets.apply('managerEntries', []), options.presets.apply<Record<string, string>>('env'), ]); const entryPoints = configDirManagerEntry ? [...managerEntriesFromPresets, configDirManagerEntry] : managerEntriesFromPresets;这些入口随后交由 esbuild 统一打包(bundle: true、format: 'iife'、输出到sb-addons目录,builder-manager/index.ts),媒体资源按 dataurl 内联、JSX 采用React.createElement转换,最终产物被注入 Storybook 管理端页面。可以推断,你在浏览器里看到的所有插件面板、工具条,本质都是这些 managerEntries 打包后的运行结果。
此外,Storybook 内核自身也通过 preset 机制贡献了必要的 manager 入口:common-preset.ts 导出的managerEntries会前置注入common-manager.js,保证管理端的基础 UI(如核心面板框架)先于插件代码注册:
export const managerEntries = async (existing: any) => { return [ pathe.join(resolvePackageDir('storybook'), 'dist/core-server/presets/common-manager.js'), ...(existing || []), ]; };这从源码层面印证了managerEntries是一个"按声明顺序累积、后声明的追加在末尾"的数组式约定。
实战:注册 Preset 类插件时该注意什么
结合上面的原理,在实际项目中使用addons注册 preset 时,可以遵循以下实践:
- 优先使用无后缀的包名。对
addon-a11y、addon-actions这类官方插件,直接写包名即可(参考 storybook-main-register-example-addon.md),Storybook 会自动探测并补全入口;/preset后缀主要用于需要显式指定 preset 入口的场合,例如文档中@storybook/addon-docs/preset的写法。 - 需要传参时改用对象形式,在
options中声明插件配置(如addon-styling-webpack的rules),详见 main-config-addons.md 中的对象示例。 - 区分 UI 插件与构建型 preset。如果你的需求只是往管理端加一个面板,只需
managerEntries;如果还要注入 preview 参数或构建配置,则需同时提供previewAnnotations与构建钩子。参考 writing-presets.mdx 对两类 preset 文件的职责划分。 - 注意入口顺序。
managerEntries以数组顺序累积,公共入口(如common-manager.js)被内核前置注入,插件入口追加其后;自定义 preset 的managerEntries函数应始终保留entry参数中的既有内容,避免破坏其他插件的 UI 注册。 - 验证入口是否生效。如果自研插件注册后管理端 UI 未出现,可检查包的
manager入口文件能否被resolveEntryFile解析到(presets.ts),并确认该包在package.json中声明了正确的exports映射——源码中专门针对exports声明做了exportsSpecifier解析优化,声明缺失可能导致入口探测失败。
进一步阅读
- 插件编写总览:writing-presets.mdx,涵盖根 preset 与本地 preset 的分工、
managerEntries与addons两种注册 API 的关系 addons配置参考:main-config-addons.mdx 与完整示例 main-config-addons.md- 官方插件落地实例:
addon-docs的 preset 出口 preset.js,以及解析器实现 presets.ts、管理端打包器 builder-manager/index.ts、内核公共入口注入 common-preset.ts
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考