ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Storybook 复用既有 Webpack 配置:在 main 配置中合并自定义 webpack.config.js 的完整指南

Storybook 复用既有 Webpack 配置:在 main 配置中合并自定义 webpack.config.js 的完整指南 Storybook 复用既有 Webpack 配置在 main 配置中合并自定义 webpack.config.js 的完整指南【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybookStorybook 的 Webpack builder 允许你在不放弃项目既有构建配置的前提下把应用原有的webpack.config.js直接导入 Storybook 的.storybook/main.js|ts通过webpackFinal钩子将两套配置合并从而让故事stories的编译行为与应用保持一致。本文基于 Storybook 官方文档的 storybook-main-using-existing-config.md 展开结合 builder-webpack5 的源码调用链完整讲解配置写法、合并规则、注意事项与调试手段读完即可在真实项目中落地这套复用既有配置的方案。一、为什么需要复用既有 Webpack 配置Storybook 在展示组件时会使用 Webpack 将组件源码、样式、静态资源打包进自己的 preview iframe。官方默认配置zero-config覆盖了大部分常见场景图片等静态文件导入、.json导入为 JavaScript 对象等。但当你遇到以下情况时默认配置就不够了应用使用了自定义 loader如 SVG 转 React 组件、样式预处理器、特殊文件格式处理应用配置了复杂的resolve.alias模块别名应用由 Vue CLI、CRA 等生成器脚手架生成其 Webpack 配置由生成器统一管理需要让 Storybook 的编译结果与应用保持完全一致。此时官方推荐的做法就是导入你既有的 Webpack 配置并合并进 Storybook 的默认配置。相关说明见 docs/configure/webpack.mdx 与 docs/builders/webpack.mdx。二、核心写法在 main 配置中导入并合并既有配置webpackFinal是.storybook/main.js|ts中的一个配置字段它的值是一个异步函数接收 Storybook 的默认 Webpack 配置对象作为第一个参数返回最终使用的配置对象。下面这段来自官方代码片段 storybook-main-using-existing-config.md 的写法展示了如何把应用根目录的webpack.config.js的 loader 规则合并进 Storybook1. CSF 3 时代的标准写法.js// .storybook/main.js import custom from ../webpack.config.js; // 导入应用既有的 Webpack 配置 export default { // 替换为你在使用的框架例如 react-webpack5、nextjs、angular 等 framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { return { ...config, module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] }, }; }, };2. CSF 3 时代的标准写法.ts// .storybook/main.ts // 替换为你在使用的框架例如 react-webpack5、nextjs、angular 等 import type { StorybookConfig } from storybook/your-framework; import custom from ../webpack.config.js; // 导入应用既有的 Webpack 配置 const config: StorybookConfig { framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { return { ...config, module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] }, }; }, }; export default config;3. CSF Next 写法React 等框架.ts// .storybook/main.ts // 替换为你在使用的框架例如 react-vite、nextjs import { defineMain } from storybook/your-framework/node; import custom from ../webpack.config.js; // 导入应用既有的 Webpack 配置 export default defineMain({ framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { return { ...config, module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] }, }; }, });4. CSF Next 写法.js// .storybook/main.js // 替换为你在使用的框架例如 react-vite、nextjs import { defineMain } from storybook/your-framework/node; import custom from ../webpack.config.js; // 导入应用既有的 Webpack 配置 export default defineMain({ framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { return { ...config, module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] }, }; }, });5. CSF Next 写法Angular.ts// .storybook/main.ts import { defineMain } from storybook/angular/node; import custom from ../webpack.config.js; // 导入应用既有的 Webpack 配置 export default defineMain({ framework: storybook/angular, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { return { ...config, module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] }, }; }, });需要说明的是defineMain在仓库中的实现是一个轻量的类型辅助函数例如 angular/src/node/index.ts 与 react-vite/src/node/index.ts 中均为export function defineMain(config) { return config; }——它提供的是类型收窄与框架入口统一真正的配置生效仍依赖main对象本身。三、合并逻辑拆解这段代码到底做了什么上述代码的核心只有一行展开式module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] },它等价于三个动作保留 Storybook 默认配置中module下的全部既有属性...config.module保留 Storybook 默认的全部module.rules规则...config.module.rules追加应用webpack.config.js中的全部module.rules规则...custom.module.rules。由于展开顺序是默认规则在前、应用规则在后后续匹配的规则会在 Webpack 的 rule 匹配中按顺序生效。这种写法在效果上等于用应用自带的 loader 规则扩展部分情况下是覆盖Storybook 的默认 loader 行为官方文档称之为 replace the loaders from Storybook with the ones from your apps webpack.config.js见 docs/builders/webpack.mdx。同理...config的展开保证了entry、output、resolve等顶层字段不被误删。四、源码视角webpackFinal 与自定义配置的加载顺序要理解这段配置为什么能生效可以追踪 builder 的预设调用链。在 builder-webpack5/src/presets/custom-webpack-preset.ts 中export async function webpack(config: Configuration, options: Options) { const { configDir, configType, presets } options; const coreOptions await presets.apply(core); let defaultConfig config; if (!coreOptions?.disableWebpackDefaults) { defaultConfig await createDefaultWebpackConfig(config, options); } const finalDefaultConfig await presets.apply(webpackFinal, defaultConfig, options); // ... }执行顺序是先生成默认配置createDefaultWebpackConfig再通过presets.apply(webpackFinal, ...)把你在main中定义的webpackFinal函数应用上去——这正是你的合并逻辑注入的位置。函数返回的配置最终被用于渲染 Storybook 的 preview iframe。另外值得注意的是main.js里的webpackFinal与项目根目录的webpack.config.js是两套并行的机制。仓库通过 load-custom-webpack-config.ts 在configDir即.storybook目录中探测webpack.config或webpackfile文件const webpackConfigs [webpack.config, webpackfile]; export const loadCustomWebpackConfig async (configDir: string) serverRequire(webpackConfigs.map((configName) resolve(configDir, configName)));如果探测到的自定义配置是一个函数builder 会进入全控制模式full-control modeif (typeof customConfig function) { logger.info(Loading custom Webpack config (full-control mode).); return customConfig({ config: finalDefaultConfig, mode: configType }); }此时你既有的配置函数会收到{ config, mode }两个参数并完全接管最终配置的生成。而本文介绍的webpackFinal合并方式则属于增量扩展路径二者互为补充。五、合并时务必保留的关键项Storybook 文档docs/configure/webpack.mdx 与 docs/builders/webpack.mdx反复强调webpackFinal中你负责自己合并配置并且必须小心保留以下内容entryStorybook preview 的入口覆盖会导致组件无法加载output产物输出配置覆盖可能导致构建产物错乱HtmlWebpackPluginStorybook 依赖它生成 preview 页面因此对config.plugins应使用追加而非整体覆盖// .storybook/main.js export default { framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { config.plugins.push(/* 你的插件 */); return config; }, };.ejs扩展名排除如果你引入的 loader 没有通过test属性显式限定文件扩展名则必须在该 loader 中exclude掉.ejs避免干扰 Storybook 自身的模板处理。六、按环境差异化配置利用 configTypewebpackFinal函数的第二个参数是 Storybook 的 options 对象其中configType字段用于区分运行模式DEVELOPMENT或PRODUCTION。当你复用的既有配置在不同环境下行为不同时可以据此分支处理写法见 main-config-webpack-final.md// .storybook/main.js export default { framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config, { configType }) { if (configType DEVELOPMENT) { // 开发模式下的额外调整 } if (configType PRODUCTION) { // 生产构建storybook build下的额外调整 } return config; }, };七、生成器项目的特殊场景官方文档特别提醒通过生成器如 Vue CLI初始化的项目某些特性需要导入生成器自身的 Webpack 配置文件才能与 Storybook 协同工作例如node_modules/vue/cli-service/webpack.config.js见 docs/configure/webpack.mdx。这类配置同样可以通过本文的webpackFinal合并方式导入。对于其他生成器应查阅其对应文档确认配置文件的准确路径与导出结构。八、配套场景TypeScript 模块别名解析如果你的应用通过tsconfig配置了路径别名如/components/...Storybook 的默认 Webpack 配置可能无法解析这些别名导致Cannot find module报错。官方提供的配套方案是在webpackFinal中接入tsconfig-paths-webpack-plugin完整写法见 storybook-main-ts-module-resolution.md// .storybook/main.ts import type { StorybookConfig } from storybook/your-framework; import TsconfigPathsPlugin from tsconfig-paths-webpack-plugin; const config: StorybookConfig { framework: storybook/your-framework, stories: [../src/**/*.mdx, ../src/**/*.stories.(js|jsx|mjs|ts|tsx)], webpackFinal: async (config) { if (config.resolve) { config.resolve.plugins [ ...(config.resolve.plugins || []), new TsconfigPathsPlugin({ extensions: config.resolve.extensions, }), ]; } return config; }, }; export default config;对于 Next.js、Nuxt 这类自带默认别名的框架则无需额外安装插件直接通过webpackFinal为resolve.alias补充与框架一致的别名即可示例见 storybook-main-ts-module-resolution-atsign-import.md。九、验证与调试查看实际生效的 Webpack 配置合并后的配置是否正确可以通过 Storybook CLI 直接输出最终配置进行核对见 docs/builders/webpack.mdx# 开发模式 yarn storybook dev --debug-webpack # 生产构建 yarn storybook build --debug-webpack运行后在终端输出中检查module.rules是否包含应用既有的规则、entry/output/plugins是否符合预期即可快速定位合并时被误覆盖的字段。十、相关文档索引配置入口总览docs/configure/index.mdxwebpackFinalAPI 参考docs/api/main-config/main-config-webpack-final.mdxWebpack builder 完整说明含lazyCompilation、fsCache等构建器选项docs/builders/webpack.mdxWebpack 配置扩展指南docs/configure/webpack.mdx相关代码片段storybook-main-using-existing-config.md、storybook-main-simplified-config.md底层实现custom-webpack-preset.ts、load-custom-webpack-config.ts小结复用既有 Webpack 配置的核心就一句话在.storybook/main.js|ts中导入应用的webpack.config.js并用webpackFinal把它的module.rules以及按需的plugins、resolve合并进 Storybook 默认配置。合并时守住entry、output与HtmlWebpackPlugin这三条底线善用configType区分开发/生产环境再配合--debug-webpack验证结果即可让 Storybook 的编译行为与应用侧保持高度一致把精力集中在组件开发本身。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进