深入 Storybook 高级配置:用 .storybook/main 中的 viteFinal、webpackFinal 与 babel 定制构建管线

发布时间:2026/9/18 21:53:51
深入 Storybook 高级配置:用 .storybook/main 中的 viteFinal、webpackFinal 与 babel 定制构建管线 深入 Storybook 高级配置用 .storybook/main 中的 viteFinal、webpackFinal 与 babel 定制构建管线本文围绕 Storybook 官方文档中「编写 Preset 插件」一节引用的.storybook/main.js|ts高级配置示例展开讲解如何把主配置文件当作一个「私有 Preset」使用通过viteFinal、webpackFinal、babel三个异步钩子深度定制 Storybook 的构建流程并给出 CSF 3 与实验性 CSF Next 两种主流写法在 React、Vue、Angular、Web Components 等框架下的完整示例。读完本文你将能在自己的 Storybook 项目中精准注入 Vite/Webpack 插件、改写 Babel 配置并理解这些钩子在源码中的执行位置与适用边界。背景主配置文件为何是一种「私有 Preset」在 Storybook 的插件体系中Preset预设是一组预先配置好的设置用来为你的环境快速装配一组特性、功能或集成。Storybook 官方将其分为两类见 docs/addons/writing-presets.mdx本地 PresetLocal presets把与插件自身相关的配置封装起来包括 builder 支持、Babel 或第三方集成根级 PresetRoot-level presets面向使用者负责通过previewAnnotations与managerEntries自动注册插件无需用户额外配置。而.storybook/main.js|ts这个主配置文件本质上就是一个「私有 Preset」——它包含的配置项主要面向开发期定制而不是分发给终端用户。通过它你可以不发布任何插件就能直接修改 Storybook 的行为与功能。本文介绍的高级配置示例正是该能力的最直接体现在同一个文件中同时提供viteFinal、webpackFinal、babel三个入口分别拦截 Vite、Webpack 与 Babel 的最终配置。关于主配置文件的整体定位它是相对于项目根目录的.storybook/main.js|ts且必须是合法 ESM即使用import而非require同时不能依赖__dirname/__filename可参见 docs/api/main-config/main-config.mdx。完整的「高级配置」骨架示例下面的骨架正是本文讨论的核心文档docs/_snippets/storybook-main-advanced-config-example.md所展示的内容三个异步钩子依次接收配置对象并原样返回或修改后返回。按当前文档体系该示例同时覆盖两种主配置编写范式CSF 3当前主流直接export default一个普通对象TS 下用框架包导出的StorybookConfig类型做约束CSF Next 实验性改用defineMain(...)工厂函数从storybook/framework/node导入并获得类型推断与将来的迁移兼容。CSF 3 写法JavaScript 版本.storybook/main.jsexport default { viteFinal: async (config, options) { // Update config here return config; }, webpackFinal: async (config, options) { // Change webpack config return config; }, babel: async (config, options) { return config; }, };TypeScript 版本.storybook/main.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, angular, etc. import type { StorybookConfig } from storybook/your-framework; const config: StorybookConfig { viteFinal: async (config, options) { // Update config here return config; }, webpackFinal: async (config, options) { // Change webpack config return config; }, babel: async (config, options) { return config; }, }; export default config;需要注意示例中storybook/your-framework是占位符实际项目应替换成你正在使用的框架包例如storybook/react-vite、storybook/nextjs、storybook/vue3-vite、storybook/angular等。StorybookConfig类型的字段覆盖整个主配置结构而不仅是这里的三个钩子。CSF Next写法CSF Next 范式要求从框架包的/node子路径导入defineMain。文档中 React 框架明确给出其可组合范围react-vite、nextjs、nextjs-vite。React.storybook/main.ts// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { defineMain } from storybook/your-framework/node; export default defineMain({ viteFinal: async (config, options) { // Update config here return config; }, webpackFinal: async (config, options) { // Change webpack config return config; }, babel: async (config, options) { return config; }, });Vue.storybook/main.tsimport { defineMain } from storybook/vue3-vite/node; export default defineMain({ viteFinal: async (config, options) { // Update config here return config; }, webpackFinal: async (config, options) { // Change webpack config return config; }, babel: async (config, options) { return config; }, });Angular.storybook/main.tsimport { defineMain } from storybook/angular/node; export default defineMain({ viteFinal: async (config, options) { // Update config here return config; }, webpackFinal: async (config, options) { // Change webpack config return config; }, babel: async (config, options) { return config; }, });Web Components.storybook/main.tsimport { defineMain } from storybook/web-components-vite/node; export default defineMain({ viteFinal: async (config, options) { // Update config here return config; }, webpackFinal: async (config, options) { // Change webpack config return config; }, babel: async (config, options) { return config; }, });对于不使用 TypeScript 的项目同样可以在.storybook/main.js中采用defineMain写法导入路径与各框架一致例如 Vue 为storybook/vue3-vite/node。需要留意的是把三个钩子同时写进一个文件并不代表它们会同时生效——Storybook 的每个项目在同一时刻只会使用一个 builderVite 或 Webpack。同时声明它们通常是为了让同一份配置能够覆盖两种 builder 场景例如在 preset / 模板中做兜底具体生效与否取决于你实际配置的 builder见下文各钩子的适用条件。三个钩子的签名、语义与适用边界综合官方 API 文档viteFinal、webpackFinal、babel可以对上例中的三个钩子做如下总结钩子签名作用生效前提viteFinal(config: Vite.InlineConfig, options: Options) Vite.InlineConfig \| PromiseVite.InlineConfig在使用Vite builder时定制 Storybook 的 Vite 配置可追加/覆盖插件、resolve 别名、css 处理等项目通过core.builder或框架默认使用storybook/builder-vitewebpackFinalasync (config: Config, options: WebpackOptions) Config在使用Webpack builder时定制 Storybook 的 Webpack 配置可追加 loader、plugin、resolve 等项目使用storybook/builder-webpack5等 Webpack 系 builderbabel(config: Babel.Config, options: Options) Babel.Config \| PromiseBabel.Config定制 Storybook 的 Babel 配置仅对内部走 Babel 编译的框架生效若框架使用 SWC 或 esbuild 编译器则该配置会被忽略三个函数均接收两个参数config当前构建阶段的完整配置对象Vite 的InlineConfig/ Webpack 的Config/ Babel 的Config。你应当修改并返回该对象而不是返回一个全新的对象options至少包含{ configType?: DEVELOPMENT | PRODUCTION }用于区分当前是开发模式storybook dev还是生产构建storybook build适合做环境相关的差异化处理。API 文档同时注明options中还存在其他难以逐一列举的字段可在编辑器内通过类型定义自行检索。关于babel钩子的两个细节官方建议如果你是插件作者应当优先使用babelDefault而非babel。babelDefault会在任何用户 preset 生效之前应用到 preview 配置上保证用户侧仍有覆盖余地而主配置中的babel位于用户配置层优先级更高。现有配置自动生效如果项目中已经存在.babelrc之类的 Babel 配置文件Storybook 会自动检测并使用它无需额外配置。另外只有在启用storybook/addon-webpack5-compiler-babel的前提下babel钩子传入的才是 Babel 官方 options。一个更贴近日常的组合示例在真实项目中viteFinal最常见的用法是追加 Vite 插件、webpackFinal最常见的用法是往config.plugins中 push 插件babel则常用来给 Storybook 的编译流程补充 JSX 之外的语法支持。例如// .storybook/main.tsreact-vite 项目示意 import type { StorybookConfig } from storybook/react-vite; const config: StorybookConfig { stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-essentials, storybook/addon-interactions], framework: storybook/react-vite, viteFinal: async (config, { configType }) { // 例如仅为开发模式注册自定义 Vite 插件 if (configType DEVELOPMENT) { // config.plugins.push(myVitePlugin()); } return config; }, webpackFinal: async (config) { config.plugins?.push(/* 仅在使用 Webpack builder 时生效 */); return config; }, }; export default config;关于该组合写法的更精简版本含framework、stories、单一webpackFinal可参考 docs/_snippets/storybook-main-simplified-config.md。钩子何时被调用从源码看执行时机三个钩子都运行在 Storybook 核心的构建流程内属于配置链configuration chain的末端环节即 Storybook 会先汇总框架、addon 等各方产出的默认配置再执行主配置文件中这些*Final钩子做最后一次改写。官方把这一类以*Final命名的钩子统称为最终改写入口configType的取值则由启动命令决定运行storybook dev时为DEVELOPMENT运行storybook build时为PRODUCTION。从本仓库源码来看defineMain在框架侧的实现非常轻量——它只是把传入对象原样返回本质是一个提供类型推导与未来兼容能力的工厂包装器。以 React 相关框架为例其完整实现位于 code/frameworks/react-vite/src/node/index.tsimport type { StorybookConfig } from ../types.ts; export function defineMain(config: StorybookConfig) { return config; } export type { StorybookConfig };由此可以推断defineMain并不在运行时改变配置内容真正的价值在于让 TypeScript 把config约束为完整的StorybookConfig结构使钩子参数获得类型提示并为后续 CSF Next 演进留出兼容空间。各框架包在/node子路径下导出defineMain的目录结构可从 code/frameworks/react-vite/src/node 等位置继续探查vue3-vite、angular、web-components-vite 等框架的结构一致。写法选型建议与注意事项综合上文在落地时建议遵循以下几点优先使用与项目框架匹配的导入。CSF 3 的 TS 写法从storybook/your-framework导入StorybookConfigCSF Next 从storybook/your-framework/node导入defineMain当前主配置 API 的完整字段清单framework、stories、addons、features、typescript、staticDirs等见 docs/api/main-config/main-config.mdx。钩子按 builder 生效。同时配置三个钩子是合法的但每个运行环境只会执行与当前 builder / 编译器匹配的那一个想覆盖「同一份 main 配置适配两种 builder」的场景时应让每个钩子保持自包含。只改配置、必返对象。config是构建器后续真正使用的对象务必在修改后return config不要丢弃它返回新字面量。开发/生产分流。用options.configType DEVELOPMENT或PRODUCTION控制仅在某模式下注入插件避免生产构建携带无用代码。文件必须为 ESM。由于.storybook/main.js|ts是私有 preset主配置文件需满足 ESM 语法约束。延伸阅读Preset 体系与 API 全景见 docs/addons/writing-presets.mdx其中「Advanced configuration」一节正是引用本文示例的出处viteFinal单钩子详解与 Options 类型见 docs/api/main-config/main-config-vite-final.mdxwebpackFinal单钩子详解见 docs/api/main-config/main-config-webpack-final.mdxbabel与babelDefault的优先级关系见 docs/api/main-config/main-config-babel.mdx 与 docs/api/main-config/main-config-babel-default.mdx主配置全部字段速览见 docs/api/main-config/main-config.mdx对应更精简的单钩子示例见 docs/_snippets/main-config-vite-final.md、docs/_snippets/main-config-webpack-final.md。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考