使用 Storybook 的 `build.test.disabledAddons` 优化测试专用构建输出

发布时间:2026/9/18 15:32:49
使用 Storybook 的 `build.test.disabledAddons` 优化测试专用构建输出 使用 Storybook 的build.test.disabledAddons优化测试专用构建输出build.test.disabledAddons是 Storybook 在main.js|ts配置中暴露的测试构建优化开关之一它允许你在运行面向性能测试storybook build --test的产物构建时把指定的 addon以及 preset从构建输出中整段移除从而精确裁剪无用代码。本文以仓库文档 main-config-test-disable-disableaddons.md 与 main-config-build.mdx 为主体结合 presets.ts 等核心源码说明它的类型签名、默认行为、名称匹配规则与典型场景读完你可以为当前项目写出可运行的裁剪配置并能理解其背后的过滤机制。为什么需要测试专用构建在 CI 或性能基准测试中往往只需要用 Storybook 渲染出的页面作为被测对象而并不需要交互面板、无障碍检查、覆盖率统计等开发/文档体验功能。Storybook 为此提供了独立的测试构建模式给storybook build命令传入--test标志时构建器会按照build.test下的开关组生成一份去掉文档生成、MDX 条目、docgen、sourcemap、tree-shaking 等负担的产物。disabledAddons是这份开关组里唯一面向第三方 addon 列表的配置它决定哪些 addon及命名空间匹配的 preset不会出现在最终的测试构建 bundle 中。根据 main-config-build.mdx 的说明build.test下的选项在传入--test标志时会自动全部生效因此这份配置的典型形态是框架 stories addons 保持不变仅在build.test中列出你想在测试构建中排除的 addon。最小可用配置三种声明风格配置位于.storybook/main.js或main.ts的顶层build.test.disabledAddons字段类型为string[]。下面是最小示例占位符your-framework需替换为实际框架名例如react-vite、nextjs、vue3-vite等export default { // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, };// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { StorybookConfig } from storybook/your-framework; const config: StorybookConfig { framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, }; export default config;以上示例把storybook/addon-a11y加入禁用列表含义是当使用storybook build --test构建测试产物时a11y addon 不会被加载进产物日常的storybook dev与常规storybook build不受影响。CSF Next 风格defineMain在新一代 CSF Next实验室特性中主配置用defineMain包裹并保持类型安全disabledAddons的写法完全一致。文档同时为多个 renderer 提供了对应写法区别仅在框架名与defineMain的导入来源// 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({ framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });// 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({ framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });import { defineMain } from storybook/vue3-vite/node; export default defineMain({ framework: storybook/vue3-vite, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });import { defineMain } from storybook/vue3-vite/node; export default defineMain({ framework: storybook/vue3-vite, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });import { defineMain } from storybook/angular/node; export default defineMain({ framework: storybook/angular, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });import { defineMain } from storybook/web-components-vite/node; export default defineMain({ framework: storybook/web-components-vite, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });import { defineMain } from storybook/web-components-vite/node; export default defineMain({ framework: storybook/web-components-vite, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], addons: [storybook/addon-a11y, storybook/addon-vitest], build: { test: { disabledAddons: [storybook/addon-a11y], }, }, });注CSF Next 仍处于实验阶段其文件名/导入路径中的node子路径用于在 Node 端而非浏览器端加载defineMain。实际使用时请把示例中的your-framework替换为项目已安装的框架包名。类型签名与参数形态从类型定义文件 core-common.ts 可以看到完整的TestBuildFlags结构export interface TestBuildFlags { /** * The package storybook/blocks will be excluded from the bundle, even when imported in e.g. the * preview. */ disableBlocks?: boolean; /** Disable specific addons */ disabledAddons?: string[]; /** Filter out .mdx stories entries */ disableMDXEntries?: boolean; /** Override autodocs to be disabled */ disableAutoDocs?: boolean; /** Override docgen to be disabled. */ disableDocgen?: boolean; /** Override sourcemaps generation to be disabled. */ disableSourcemaps?: boolean; /** Override tree-shaking (dead code elimination) to be disabled. */ disableTreeShaking?: boolean; /** Minify with ESBuild when using webpack. */ esbuildMinify?: boolean; }可见disabledAddons是数组类型每一项就是一个 addon 标识符通常是包名也可以包含子路径如storybook/addon-essentials/docs用于构成TestBuildConfig下的build.test字段。底层原理addon 过滤发生在 preset 加载阶段为什么disabledAddons能禁用某个 addon关键在于 Storybook 把所有 addon 与 preset 都视作可递归加载的 preset 条目而disabledAddons的过滤发生在这一加载管线中。看 presets.ts 的实现let filter (i: PresetConfig) { return true; }; if ( storybookOptions.isCritical ! true (storybookOptions.build?.test?.disabledAddons?.length || 0) 0 ) { filter (i: PresetConfig) { // ts-expect-error (Converted from ts-ignore) const name i.name ? i.name : i; return !storybookOptions.build?.test?.disabledAddons?.find((n) name.includes(n)); }; } const subPresets resolvePresetFunction( presetsInput, presetOptions, storybookOptions ).filter(filter); const subAddons resolvePresetFunction(addonsInput, presetOptions, storybookOptions).filter( filter );这段代码揭示了几条重要规则过滤作用域过滤同时作用于当前 preset 的addons输入和presets输入也就是说不仅 addon 本身命名空间中声明的子 preset 也会被一并过滤掉。名称匹配是包含式子串匹配判断条件为name.includes(n)——只要 preset 解析出的名字包含你在disabledAddons中写的字符串就命中。因此写storybook/addon-a11y会匹配该 addon 的全部入口而写一个过短的片段如a11y有更大范围的误伤风险同理这也是官方默认列表能用storybook/addon-essentials/docs这种子路径精确命中单个入口的原因。isCritical豁免对关键 presetisCritical true该过滤不会生效避免因为误禁用导致构建框架本身无法加载。--test模式下 disabledAddons 的默认值main-config-build.mdx 明确指出build.test下的选项在传入--test标志时自动生效。实际实现位于 common-override-preset.tsconst createTestBuildFeatures (value: boolean): RequiredTestBuildFlags ({ disableBlocks: value, disabledAddons: value ? [storybook/addon-docs, storybook/addon-essentials/docs, storybook/addon-coverage] : [], disableMDXEntries: value, disableAutoDocs: value, disableDocgen: value, disableSourcemaps: value, disableTreeShaking: value, esbuildMinify: value, }); export const build: PresetPropertybuild async (value, options) { return { ...value, test: options.test ? { ...createTestBuildFeatures(!!options.test), ...value?.test, } : createTestBuildFeatures(false), }; };含义很清晰只要进入--test构建Storybook 就会默认禁用storybook/addon-docs、storybook/addon-essentials/docs与storybook/addon-coverage而你的主配置build.test中的字段通过展开...value?.test逐项覆盖默认值。因此使用disabledAddons的正确姿势是默认裁剪docs、coverage 等已经内置你只需要追加属于自己项目且会影响测试结果的 addon例如本文示例中的storybook/addon-a11y用于在你只关注 DOM 结构/截图比对的场景中去掉无障碍扫描逻辑。反过来如果你确实需要在测试构建中保留某个被默认禁用的 addon也可以通过主配置显式把对应条目从数组中移除因为用户配置会整体覆盖disabledAddons数组而非做并集合并。单元测试如何验证该行为仓库在 presets.test.ts 中为这一机制提供了可运行的验证用例。测试构造了一个虚拟 preset其presets含storybook/preset-typescript、addons含storybook/addon-docs与addon-bar随后以build.test.disabledAddons: [storybook/addon-docs]调用loadPreset并用快照断言结果storybook/addon-docs不应出现在顶层条目中而storybook/preset-typescript与addon-bar应被保留。这个用例与 presets.ts 的过滤逻辑一一对应是理解过滤影响 addon 与 preset、但不会波及未命中的条目最直观的证据。组合使用与其他build.test开关的分工disabledAddons只解决移除整个 addon/preset这一类问题。若你的优化目标是代码体积或文档产物应把它与同级的其他开关配合。以下为TestBuildFlags全部可用项及语义类型与注释见 core-common.ts配置项类型作用disabledAddonsstring[]在构建输出中禁用指定 addon/preset本主题disableBlocksboolean即使 preview 中 import 了storybook/blocks也将其排除出 bundledisableMDXEntriesboolean过滤掉用户手写的.mdxstories 条目disableAutoDocsboolean覆盖 autodocs 行为测试构建不再生成自动文档disableDocgenboolean关闭 docgen 静态分析推断见 common-override-preset.ts 对typescriptpreset 的处理同时把reactDocgen与check置为falsedisableSourcemapsboolean覆盖默认的 sourcemap 生成行为disableTreeShakingboolean关闭 tree-shaking死代码消除esbuildMinifybooleanwebpack 场景下使用 ESBuild 做压缩实战建议与注意事项只动build.test别动顶层addons被禁用的 addon 仍然服务于开发模式与普通构建把disabledAddons限制在测试构建范围内可以保证storybook dev的开发体验不受影响。官方文档建议仅在确实需要关闭某特性或排查构建问题时才显式覆盖--test默认行为。理解子串匹配因为判断是name.includes(n)条目尽量写完整包名同时可借此特性用子路径如storybook/addon-essentials/docs做更精细的定向裁剪。验证生效范围如果发现某个 addon 仍出现在测试产物中优先检查它是否经由isCritical的关键 preset 注册或名字是否与你写的字符串匹配。多框架写法一致无论 CSF 3 还是 CSF Next、无论 react/vue/angular/web-componentsbuild.test.disabledAddons的字段结构完全相同仅framework值与CSF Next 下defineMain的导入来源不同示例可直接套用。结合 主配置说明 与 build 配置参考 阅读本主题可进一步掌握main.js|ts中framework、stories、addons与build的整体关系需要从零启用测试构建时为storybook build追加--test标志即可让本文全部配置生效。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考