资讯详情

为 Storybook Preset Addon 编写 Preview 注解模块:从 decorators 与 globals 到 CSF Next 的 definePreview

📅 2026/9/10 13:53:43 | 华诺云谱 👁 阅读
为 Storybook Preset Addon 编写 Preview 注解模块:从 decorators 与 globals 到 CSF Next 的 definePreview
为 Storybook Preset Addon 编写 Preview 注解模块从 decorators 与 globals 到 CSF Next 的 definePreview【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook本篇文章面向正在开发 Storybook 预设型插件preset addon的开发者讲解如何编写 addon 的preview 模块即项目级注解project annotations的载体并通过 root-level preset 的previewAnnotationsAPI 将全局装饰器与全局变量注入用户的所有 story。读完你将掌握经典 CSF 3 导出与实验性 CSF NextdefinePreview两种写法以及 Storybook 底层如何解析 addon 的 preview 入口文件。文章代码主体源自 storybook-addons-preset-preview.md该片段被官方文档 writing-presets.mdx 在讲解previewAnnotationsAPI 时引用。Preview 模块在 preset 中的角色在 Storybook 的预设体系里一个完整 preset 通常拆成两部分本地 presetlocal preset封装插件自身配置例如 babelDefault、viteFinal、webpackFinal等面向 builder 的 APIroot-level preset面向最终用户负责把 addon 注册进 StorybookpreviewAnnotations与managerEntries是它的两个关键出口。previewAnnotations负责“给渲染 story 的预览侧注入额外代码”例如 decorators 或 parameters原文档以 writing-stories/parameters.mdx 相对路径形式引用此处对应仓库目录 writing-stories。官方配置项文档 main-config-preview-annotations.mdx 将该字段类型定义为string[] | ((config: string[], options: Options) string[] | Promisestring[])即一个脚本路径数组或返回数组的函数其语义是“在 story preview 中额外运行的脚本”。也就是说想让 addon 的装饰器对用户所有 story 生效最干净的做法不是让用户手动改.storybook/preview.js而是由 addon 自带一份 preview 模块再让 root preset 通过previewAnnotations指向它。第一步创建 preview 注解模块经典 CSF 3 导出官方推荐的结构是 addon 源码目录下的example-addon/src/preview.js|ts构建后输出为dist/preview.js模块默认导出一个包含decorators与globals的注解对象。JS 版本如下import { PARAM_KEY } from ./constants; import { CustomDecorator } from ./decorators; const preview { decorators: [CustomDecorator], globals: { [PARAM_KEY]: false, }, }; export default preview;若使用 TypeScript可以借助 Storybook 导出的Renderer与ProjectAnnotations类型获得完整类型提示import type { Renderer, ProjectAnnotations } from storybook/internal/types; import { PARAM_KEY } from ./constants; import { CustomDecorator } from ./decorators; const preview: ProjectAnnotationsRenderer { decorators: [CustomDecorator], globals: { [PARAM_KEY]: false, }, }; export default preview;理解这段代码的关键在于它写的是什么级别的注解decorators: [CustomDecorator]全局装饰器。只要它出现在 project-level 注解中就会被应用到该 Storybook 实例内每一个 story包括 docs 模式下的内嵌 canvas不需要用户为每个 story 单独包裹。CustomDecorator与PARAM_KEY分别从 addon 自身的./decorators、./constants模块导入前者负责实际渲染包装例如注入 Provider、主题容器、国际化上下文后者是插件用于在参数/全局变量中命名空间的常量键惯例形如myAddonglobals: { [PARAM_KEY]: false }全局变量默认值。用计算属性键[PARAM_KEY]声明该 addon 相关的 global 默认关闭。globals是可由用户在工具栏切换、且在导航与 story 之间保持一致的全局状态类型层面由BaseProjectAnnotations定义Globals、GlobalTypes等类型均可在仓库中code/core/src/types/modules/csf.ts顶部看到其 re-export见 csf.ts。从类型结构看ProjectAnnotations定义在 code/core/src/types/modules/story.ts它继承自BaseProjectAnnotationsdecorators、globals、parameters、loaders等项目级字段的基础载体并追加了renderToCanvas、testingLibraryRender等渲染器相关内容而storybook/internal/types只是该类型仓库在当前 monorepo 内的再导出入口。需要强调这段 preview 模块本身并不负责注册 UI 面板那是manager入口与managerEntries的职责它只影响故事渲染的预览侧。第二步用 root preset 的 previewAnnotations 暴露给用户有了preview模块后还需要 root-level preset通常位于 addon 包根目录的example-addon/preset.js把它声明出去。官方 root preset 示例见 storybook-addons-root-preset.mdexport const previewAnnotations [import.meta.resolve(./dist/preview)]; export const managerEntries [import.meta.resolve(./dist/manager)]; export * from ./dist/preset.js;这里有几个值得注意的实现细节import.meta.resolve(./dist/preview)会在addon 构建产物如dist/preview.js上解析出绝对路径保证用户工程无论使用 Webpack、Vite 还是其他打包器都能稳定命中该入口。这也是官方 TS/JS 示例统一使用import.meta.resolve的原因用户只需在.storybook/main.js的addons数组里写入 addon 包名例如example-addonStorybook 就会自动解析并合并previewAnnotations无需用户手动复制任何 preview 配置——这正是 preset 把“重复的接入样板”收敛到 addon 内部的价值如果你要加载的第三方 addon 需要额外配置可使用函数形态的managerEntries见 storybook-addons-root-preset-manager-entries.md。底层如何解析 addon 的 preview 入口Storybook 核心在解析 addon 时会尝试定位名为preset、manager、preview的入口文件并分别组装预设。相关逻辑位于 code/core/src/common/presets.ts它通过resolveEntryFile(preview)探测 preview 文件命中后即把该路径 push 进previewAnnotations数组同时将preset.js作为子预设、manager作为 manager 入口一并收集若三者都不存在则回退为纯 preset 解析。仓库测试 code/core/src/common/presets.test.ts 也印证了这一行为——测试用例把previewAnnotations配置为指向 addon 目录下的preview.js验证解析结果能正确携带注解条目。因此当你在 addon 里看到preview.js、manager.js、preset.js三件套并存时preview.js就是“被previewAnnotations自动加载的渲染侧注解”而它不必出现在你的 preset 导出中——只要被命名约定解析到即可。第三步CSF Next 实验性写法definePreview文档在同一片段中还提供了CSF Next 实验性的两种变体分别使用.tsx/.jsx扩展名并改为调用框架提供的definePreview()工厂函数。TypeScript React 示例如下import type { ProjectAnnotations, Renderer } from storybook/internal/types; // Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { definePreview } from storybook/your-framework; import { PARAM_KEY } from ./constants; import { CustomDecorator } from ./decorators; export default definePreview({ decorators: [CustomDecorator], globals: { [PARAM_KEY]: false, }, });纯 JavaScript 版本结构一致只是省略了类型标注// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { definePreview } from storybook/your-framework; import { PARAM_KEY } from ./constants; import { CustomDecorator } from ./decorators; export default definePreview({ decorators: [CustomDecorator], globals: { [PARAM_KEY]: false, }, });这种新写法与经典导出的差别体现在三处必须从框架包导入工厂函数代码注释明确要求把storybook/your-framework替换成实际使用的框架例如storybook/react-vite、storybook/nextjs、storybook/nextjs-vite。框架包导出definePreview意味着它可以拿到该框架正确的Renderer泛型从而让decorators、globals、parameters的键与值在编写期就具备精确的类型推断文件后缀改为.tsx/.jsx扩展名本身是提示 Storybook 按 CSF Next即definePreview风格处理该 preview 模块的信号之一因此经典导出的.js/.ts与 CSF Next 的.jsx/.tsx需要区分开来文档保留两个语族便于迁移期新旧并存导出方式统一为export default definePreview({...})把注解对象交给工厂函数而不是直接export default preview由框架负责后续规范化。由于仓库注释将该能力标注为 “” 实验性 API同一代码片段在 addon-migration-guide.mdx 与 csf-next.mdx 中也有呼应在生产 addon 中采用前建议同时评估团队对实验性特性的接受度经典 CSF 3 导出目前仍是稳定路径。Preview 模块的合并顺序与生效范围当previewAnnotations命中多个入口用户自己的.storybook/preview.js、各 addon 的 preview 模块时Storybook 会按注解顺序合并后统一交给渲染进程。从 code/core/src/types/modules/core-common.ts 与 story.ts 的类型定义可以确认previewAnnotations是Entry[]可被 preset 链逐级扩展而注解的合并发生在项目级project annotations维度优先级顺序为addon/framework 注入的注解 → 用户在.storybook/preview.js中书写的注解后者可覆盖前者同名字段如同名 decorator 顺序、同 key 的 globals 默认值。对 addon 作者而言这意味着你在 preview 模块里设置的globals[PARAM_KEY]只是默认值最终用户仍可在.storybook/preview.js覆盖也可在工具栏中实时切换decorator 会被包裹在所有用户 story 之外但仍遵循“先注册先包裹”的顺序若与用户全局装饰器存在相互依赖如 Provider 必须在消费组件外层要格外注意解析顺序。编写与发布 preview 模块的最佳实践结合上面的官方片段与源码可以把实践经验归纳为以下几条保持 preview 模块轻量、类型隔离类型导出尽量使用import type避免把类型定义带进运行时产物Renderer、ProjectAnnotations仅作类型用途不会增加包体积build 产物与 preset 路径保持同步preset.js里通过import.meta.resolve(./dist/preview)指向编译产物因此发布前务必确保构建输出dist/preview.js以及dist/manager.js、dist/preset.js并让包exports/files字段正确包含这些文件——否则核心在resolveEntryFile(preview)见 presets.ts阶段将探测不到入口渲染侧与 UI 侧职责分离装饰器、全局变量、参数等影响故事渲染的内容放进 preview 模块通过previewAnnotations暴露面板、工具栏等界面功能放进 manager通过managerEntries暴露二者不要混写优先收敛接入成本能通过 preset 自动注入的如全局 Provider、主题、国际化就不要让用户去.storybook/preview.js手写这正是 root preset 的价值——一次addons配置即可完成接入区分 globals 与 parameters短生命周期的开关式状态如某个 addon 功能是否启用适合globals而文档渲染、控制台配置等静态配置更适合放在parameters中按命名空间下发。继续深入writing-presets.mdx —— preset 开发全貌含babelDefault、viteFinal、webpackFinal、managerEntries、previewAnnotations、previewHead/previewBody、addons、Entries等 API 的完整串联storybook-addons-root-preset.md 与 storybook-addons-root-preset-manager-entries.md —— root preset 两件套的对照阅读main-config-preview-annotations.mdx ——previewAnnotations字段的类型签名与storybook/nextjs的真实使用示例presets.ts 与 presets.test.ts —— 解析 addonpreview/manager/preset三入口的源码与测试证据story.ts 与 csf.ts ——ProjectAnnotations/BaseProjectAnnotations/Globals的类型定义出处。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。