深入 Storybook 高级配置:用 .storybook/main 中的 viteFinal、webpackFinal 与 babel 定制构建管线
2026/9/18 21:53:31 网站建设 项目流程

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

本文围绕 Storybook 官方文档中「编写 Preset 插件」一节引用的.storybook/main.js|ts高级配置示例展开,讲解如何把主配置文件当作一个「私有 Preset」使用,通过viteFinalwebpackFinalbabel三个异步钩子深度定制 Storybook 的构建流程,并给出 CSF 3 与实验性 CSF Next 两种主流写法在 React、Vue、Angular、Web Components 等框架下的完整示例。读完本文,你将能在自己的 Storybook 项目中精准注入 Vite/Webpack 插件、改写 Babel 配置,并理解这些钩子在源码中的执行位置与适用边界。

背景:主配置文件为何是一种「私有 Preset」

在 Storybook 的插件体系中,Preset(预设)是一组预先配置好的设置,用来为你的环境快速装配一组特性、功能或集成。Storybook 官方将其分为两类(见 docs/addons/writing-presets.mdx):

  • 本地 Preset(Local presets):把与插件自身相关的配置封装起来,包括 builder 支持、Babel 或第三方集成;
  • 根级 Preset(Root-level presets):面向使用者,负责通过previewAnnotationsmanagerEntries自动注册插件,无需用户额外配置。

.storybook/main.js|ts这个主配置文件,本质上就是一个「私有 Preset」——它包含的配置项主要面向开发期定制,而不是分发给终端用户。通过它,你可以不发布任何插件,就能直接修改 Storybook 的行为与功能。本文介绍的高级配置示例正是该能力的最直接体现:在同一个文件中同时提供viteFinalwebpackFinalbabel三个入口,分别拦截 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)所展示的内容:三个异步钩子依次接收配置对象并原样返回(或修改后返回)

按当前文档体系,该示例同时覆盖两种主配置编写范式:

  1. CSF 3(当前主流):直接export default一个普通对象,TS 下用框架包导出的StorybookConfig类型做约束;
  2. CSF Next(🧪 实验性):改用defineMain(...)工厂函数(从@storybook/<framework>/node导入),并获得类型推断与将来的迁移兼容。

CSF 3 写法

JavaScript 版本(.storybook/main.js):

export 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.ts):

import { 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.ts):

import { 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.ts):

import { 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 的每个项目在同一时刻只会使用一个 builder(Vite 或 Webpack)。同时声明它们通常是为了让同一份配置能够覆盖两种 builder 场景(例如在 preset / 模板中做兜底),具体生效与否取决于你实际配置的 builder(见下文各钩子的适用条件)。

三个钩子的签名、语义与适用边界

综合官方 API 文档(viteFinal、webpackFinal、babel)可以对上例中的三个钩子做如下总结:

钩子签名作用生效前提
viteFinal(config: Vite.InlineConfig, options: Options) => Vite.InlineConfig \| Promise<Vite.InlineConfig>在使用Vite builder时定制 Storybook 的 Vite 配置,可追加/覆盖插件、resolve 别名、css 处理等项目通过core.builder或框架默认使用@storybook/builder-vite
webpackFinalasync (config: Config, options: WebpackOptions) => Config在使用Webpack builder时定制 Storybook 的 Webpack 配置,可追加 loader、plugin、resolve 等项目使用@storybook/builder-webpack5等 Webpack 系 builder
babel(config: Babel.Config, options: Options) => Babel.Config \| Promise<Babel.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而非babelbabelDefault会在任何用户 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.ts(react-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;

关于该组合写法的更精简版本(含frameworkstories、单一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.ts:

import 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 等框架的结构一致)。

写法选型建议与注意事项

综合上文,在落地时建议遵循以下几点:

  1. 优先使用与项目框架匹配的导入。CSF 3 的 TS 写法从@storybook/<your-framework>导入StorybookConfig,CSF Next 从@storybook/<your-framework>/node导入defineMain;当前主配置 API 的完整字段清单(frameworkstoriesaddonsfeaturestypescriptstaticDirs等)见 docs/api/main-config/main-config.mdx。
  2. 钩子按 builder 生效。同时配置三个钩子是合法的,但每个运行环境只会执行与当前 builder / 编译器匹配的那一个;想覆盖「同一份 main 配置适配两种 builder」的场景时,应让每个钩子保持自包含。
  3. 只改配置、必返对象config是构建器后续真正使用的对象,务必在修改后return config,不要丢弃它返回新字面量。
  4. 开发/生产分流。用options.configType === 'DEVELOPMENT''PRODUCTION'控制仅在某模式下注入插件,避免生产构建携带无用代码。
  5. 文件必须为 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.mdx;
  • webpackFinal单钩子详解:见 docs/api/main-config/main-config-webpack-final.mdx;
  • babelbabelDefault的优先级关系:见 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询