Webpack 4.x 实战配置指南:排障手册与稳定构建方案
1. 项目概述为什么现在还要学 Webpack 4.x 配置Webpack 4.x 虽然已不是“最新版”——它在2018年2月发布距今已有六年多但至今仍有大量存量项目、企业级中后台系统、老版本 Vue CLI如 v3.0–v4.5、React Create App 早期定制脚手架以及大量未升级的私有构建管道仍在稳定运行 Webpack 4.x。我去年参与维护的某高校实验室教学平台前端工程就是基于 Vue 2.6 Webpack 4.41.2 构建的上个月接手的一个金融类报表系统迁移任务其构建层卡在 Webpack 4.39.3连mode: production的默认优化都因插件兼容问题被手动禁用。这不是技术怀旧而是真实存在的工程现实Webpack 4.x 不是过时的标本而是仍在呼吸、仍在报错、仍在被 daily build 的活体系统。你点开这个标题大概率不是为了追新而是正坐在工位上面对一个报错的npm run build控制台里滚动着Module not found: Error: Cant resolve xxx或者发现打包体积突然暴涨 300%又或者dev-server热更新失效、CSS 样式不生效、第三方库的 ES Module 被错误转译……这些问题在 Webpack 5 的文档里找不到答案在 Stack Overflow 上搜到的方案可能直接 crash 你的 4.x 流程。因为 Webpack 4 和 5 在核心机制上存在三处不可忽视的断层一是mode的默认行为差异4.x 中mode: production默认启用UglifyJsPlugin而 5.x 改为TerserPlugin且插件注册方式不同二是optimization.splitChunks的默认 chunkGroup 命名逻辑变更4.x 默认生成vendors~xxx.js5.x 改为vendors-node_modules-xxx三是resolve.alias对node_modules内部路径的解析优先级调整。这些细节官方迁移指南一笔带过但实操中足以让一个配置项失效整条链路。所以这篇内容不讲“如何升级到 Webpack 5”也不堆砌“最新特性列表”。它是一份面向真实战场的排障手册配置说明书避坑日志。我会从零开始用最精简但完全可运行的webpack.config.js入手逐行解释每个字段为何必须这样写、不这样写的后果是什么、哪些参数看似可选实则暗藏雷区。你会看到为什么output.path必须是绝对路径相对路径在某些 Node 版本下会静默失败为什么module.rules中test正则末尾加/i可能导致.SVG文件被漏处理为什么new HtmlWebpackPlugin()不显式传入template会导致 HTML 中 script 标签顺序错乱甚至为什么devServer.hot true单独开启无效必须配合HotModuleReplacementPlugin手动注入——这些都不是理论推演是我过去三年在 17 个不同 Webpack 4.x 项目里亲手改坏、重装、抓包、打断点后确认的硬经验。如果你正在维护一个 Webpack 4.x 工程或者需要快速搭建一个兼容老浏览器、低配 CI 环境的轻量构建流程那么接下来的内容每一行代码都值得你复制粘贴、逐字比对。2. 整体设计思路与配置选型逻辑2.1 为什么坚持 Webpack 4.x不是“守旧”而是“权衡”很多人一看到“4.x”就下意识划走觉得不如直接学 Webpack 5 或 Vite。但工程决策从来不是“新好”而是“适配稳”。我在某公司主导过一次从 Webpack 4.41 → 5.76 的升级耗时 6 周核心卡点不在语法迁移而在三个真实约束Node.js 版本锁死客户私有云 CI 环境仅支持 Node 10.15.3LTS EOL 已超期而 Webpack 5 最低要求 Node 12自定义 Loader 兼容断层内部开发的svg-sprite-loader4.2.0与 Webpack 5 的compilation.hooks.processAssetsAPI 不兼容重写需 3 人日构建产物校验规则僵化安全审计系统硬编码校验webpack-stats.json中chunks[].files字段结构Webpack 5 输出格式变更导致自动门禁失败。最终方案是保留 Webpack 4.x 主干仅将terser-webpack-plugin升级至 v4支持 ES2020 语法压缩其他模块冻结。这正是本文配置设计的底层逻辑——不追求“全栈最新”而追求“最小改动下的最大稳定性”。所有配置项的选择都围绕三个刚性目标展开零运行时异常确保dev-server启动不报错、HMR 生效、build输出可直接部署可控的产物体积通过splitChunks精准分离 vendor 与业务代码避免node_modules全量打入主包可调试的构建过程保留source-map生成能力且 dev 与 prod 模式使用不同策略dev 用cheap-module-eval-source-map平衡速度与定位精度prod 用source-map保证线上错误可追溯。提示Webpack 4.x 的mode选项虽简化了配置但它的“智能默认”恰恰是新手最大的陷阱。例如mode: development会自动启用NamedModulesPlugin和NamedChunksPlugin这在大型项目中会导致 HMR 更新时 chunk ID 泄露控制台打印./src/utils/request.js而非utils_request_js暴露源码结构而mode: production默认开启FlagDependencyUsagePlugin若你代码中存在require(xxx)动态字符串拼接会直接报Critical dependency: the request of a dependency is an expression。因此本文所有配置均显式声明关键插件而非依赖mode的黑盒行为。2.2 配置分层策略base env mergeWebpack 4.x 官方推荐使用webpack-merge实现环境配置复用但很多教程只教“怎么合并”没说“为什么必须分层”。我见过太多项目把dev和prod配置写在一个文件里用if (process.env.NODE_ENV production)大段包裹结果导致dev模式下误启TerserPlugin热更新卡顿 3 秒以上prod模式下漏关devtool生成巨量*.js.map文件拖慢 CIbuild时因DefinePlugin注入变量名拼写错误如PRODUCTION写成production导致线上功能静默失效。本文采用三级配置结构webpack.base.js基础能力层定义入口、输出、模块规则、解析配置不包含任何环境相关插件webpack.dev.js开发增强层注入HotModuleReplacementPlugin、devServer、eval-source-map等仅 dev 需要的能力webpack.prod.js生产优化层注入MiniCssExtractPlugin、TerserPlugin、SplitChunksPlugin等仅 prod 需要的优化。三者通过webpack-merge的merge.smartStrategy合并该策略能智能处理数组合并如plugins数组追加而非覆盖和对象深度合并如optimization.splitChunks.cacheGroups。关键在于merge.smartStrategy对module.rules的合并逻辑是“按test正则匹配优先级叠加”这意味着你可以在base中定义通用babel-loader在prod中追加thread-loader提升编译速度而不会因规则重复导致 Babel 执行两次。注意webpack-mergev4.2.2 是 Webpack 4.x 兼容的最高稳定版。v5 版本内部使用lodash.merge其对函数类型值的处理逻辑变更v4 会浅拷贝函数v5 会深克隆导致loader实例丢失曾导致某项目vue-loader报Cannot read property cacheDirectory of undefined。因此本文所有示例锁定webpack-merge: 4.2.2。2.3 插件选型拒绝“全家桶”只留“刚需”Webpack 4.x 生态插件繁杂但真正影响构建稳定性的核心插件不超过 7 个。我按“是否必须显式声明”分为两类插件名称是否必须显式声明原因说明HtmlWebpackPlugin✅ 必须Webpack 4.x 默认不生成 HTML且其inject: true行为依赖entry名称若 entry 为对象如{app: ./src/index.js, admin: ./src/admin.js}不显式配置chunks会导致 script 标签顺序错乱MiniCssExtractPlugin✅ 必须style-loader在mode: production下无法提取 CSS必须用此插件替代且其filename必须与output.filename保持同级目录结构否则publicPath解析失败CleanWebpackPlugin⚠️ 推荐output.clean: true是 Webpack 5 特性4.x 必须靠此插件清空dist否则旧文件残留导致线上 404CopyWebpackPlugin⚠️ 按需静态资源如favicon.ico,robots.txt需手动拷贝否则public目录内容不会自动进入distDefinePlugin✅ 必须环境变量注入必须显式声明process.env.NODE_ENV的值由DefinePlugin注入而非 Node 运行时读取这是 Webpack 4.x 的核心机制特别强调MiniCssExtractPlugin它在 Webpack 4.x 中存在一个致命陷阱——不能与style-loader共存于同一 rule。很多教程写use: [style-loader, css-loader]然后在prod中替换为[mini-css-extract-plugin/loader, css-loader]这会导致dev模式下style-loader的hmr逻辑被破坏。正确做法是在base中定义cssRuledev和prod分别用rule.use覆盖loader数组而非整个rule。3. 核心配置详解与实操要点3.1 webpack.base.js基础骨架的 12 个关键字段webpack.base.js是整个配置的基石它定义了“Webpack 知道什么、能处理什么”。以下是我经过 12 个项目验证的最小可行配置每个字段都附带实操注释const path require(path); const { DefinePlugin } require(webpack); // 1. context设置模块解析的基准目录避免相对路径混乱 // 若不设entry ./src/index.js 会被解析为相对于当前执行目录如 /project而非项目根目录 const context path.resolve(__dirname, ../); module.exports { // 2. context必须显式声明否则 resolve.alias 可能失效 context, // 3. entry支持字符串、数组、对象三种形式 // 字符串/数组适用于单页应用对象适用于多页应用MPAkey 将作为 chunk name entry: { app: ./src/index.js, // 若需兼容 IE11可额外引入 polyfill // polyfill: babel/polyfill }, // 4. output所有路径必须为绝对路径 // Webpack 4.x 对相对路径处理不稳定尤其在 Windows 下易出错 output: { // path输出目录绝对路径__dirname 是当前 config 文件所在目录 path: path.resolve(context, dist), // filename[name] 对应 entry key[hash] 用于缓存控制 // 注意[chunkhash] 在 Webpack 4.x 中需配合 optimization.splitChunks 使用否则全量更新 filename: js/[name].[hash:8].js, // publicPath静态资源 URL 前缀必须以 / 结尾 // 若部署在子路径如 https://example.com/my-app/此处应设为 /my-app/ publicPath: / }, // 5. resolve模块解析核心配置 resolve: { // extensions自动补全后缀顺序很重要.ts 应在 .js 前否则 TS 文件被当 JS 解析 extensions: [.ts, .tsx, .js, .jsx, .json], // alias路径别名提升 import 可读性 // 注意 别名必须指向绝对路径否则 resolve 失败 alias: { : path.resolve(context, src), assets: path.resolve(context, src/assets), // vue$ 后缀加 $避免匹配到 vue-router 等包 vue$: vue/dist/vue.esm.js } }, // 6. module.rules模块处理规则按顺序匹配第一个 test 成功即停止 module: { rules: [ // 7. js/ts 处理babel-loader 是 Webpack 4.x 兼容性最佳的转译器 { test: /\.(js|jsx|ts|tsx)$/, // exclude必须排除 node_modules否则 babel 处理所有依赖构建时间翻倍 // 且某些库如 antd已预编译再转译可能破坏 tree-shaking exclude: /node_modules/, use: { loader: babel-loader, options: { // presets预设集合babel/preset-env 自动按目标浏览器转译 presets: [ [babel/preset-env, { // targets指定最低兼容浏览器直接影响 polyfill 注入 // 不要写 last 2 versions应明确写 chrome 49, ie 11 targets: { chrome: 49, ie: 11 }, // useBuiltIns: usage 表示按需注入 polyfill需配合 babel/polyfill 使用 useBuiltIns: usage, corejs: 3 }], babel/preset-react, babel/preset-typescript ], // plugins额外插件如 class-properties 需单独启用 plugins: [ babel/plugin-proposal-class-properties, babel/plugin-transform-runtime ] } } }, // 8. CSS 处理分离 dev 与 prod 的 loader 链 { test: /\.css$/, // use 数组按顺序执行从右到左即 css-loader → style-loaderdev或 MiniCssExtractPlugin.loaderprod use: [ // 此处占位实际由 webpack.dev.js / webpack.prod.js 覆盖 style-loader, { loader: css-loader, options: { // modules: true 开启 CSS Modules但需注意全局样式需用 :global() modules: false, // importLoaders: 1 表示 css 中 import 的文件也经过 1 个 loader即 postcss-loader importLoaders: 1 } }, postcss-loader ] }, // 9. 图片/字体处理url-loader 优于 file-loader小文件转 base64 减少请求 { test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf|svg)$/, use: [ { loader: url-loader, options: { // limit: 8192 表示 ≤8KB 转 base64否则生成独立文件 limit: 8192, // name输出文件名[name] 为原文件名[hash:6] 为哈希前6位 name: [name].[hash:6].[ext], // outputPath输出到 dist 下的子目录 outputPath: assets/, // publicPathURL 路径与 outputPath 对应 publicPath: assets/ } } ] } ] }, // 10. plugins基础插件DefinePlugin 必须在此声明 plugins: [ // DefinePlugin注入全局常量注意字符串值必须用 JSON.stringify 包裹 // 否则 production 会被当变量名而非字符串字面量 new DefinePlugin({ process.env.NODE_ENV: JSON.stringify(process.env.NODE_ENV || development) }) ], // 11. optimizationWebpack 4.x 的核心优化配置splitChunks 是体积控制关键 optimization: { // splitChunks代码分割Webpack 4.x 默认启用但需显式配置才能生效 splitChunks: { // chunks: all 表示同时处理同步和异步代码async 仅处理动态 import chunks: all, // minSize: 30000 表示 ≥30KB 的模块才分割避免过度拆分 minSize: 30000, // maxSize: 250000 表示单个 chunk 250KB 强制二次分割防止单文件过大 maxSize: 250000, // cacheGroups缓存组按规则分组打包 cacheGroups: { // vendor 组匹配 node_modules 中的模块 vendor: { name: vendors, // test: /[\\/]node_modules[\\/]/ 正则匹配路径中的 node_modules test: /[\\/]node_modules[\\/]/, // priority: 10 表示优先级高于其他组确保 vendor 优先被提取 priority: 10, // reuseExistingChunk: true 表示若已有相同模块直接复用不新建 chunk reuseExistingChunk: true } } }, // runtimeChunk: single 将 webpack 运行时代码提取为独立文件避免 vendor 更新导致 app hash 变更 runtimeChunk: single }, // 12. externals外部依赖不打包进 bundle由 CDN 或全局变量提供 // 例如 jQuery 通过 CDN 引入则此处配置 externals: { jquery: jQuery } externals: {} };实操心得output.filename中的[hash]与[chunkhash]选择是体积控制的关键。[hash]是整个构建的 hash任一文件变更所有文件 hash 都变导致浏览器缓存全部失效[chunkhash]是单个 chunk 的 hash仅该 chunk 内容变更才变。但在 Webpack 4.x 中[chunkhash]需配合optimization.splitChunks使用否则vendor.js的 hash 会随app.js变更而改变因运行时代码耦合。因此本文采用[hash:8]runtimeChunk: single组合runtime.js单独提取app.js和vendors.js的 hash 仅由自身内容决定实测缓存命中率提升 65%。3.2 webpack.dev.js开发体验的 5 个提效细节开发配置的核心目标是“快”与“准”启动快、编译快、更新快、定位准。以下是webpack.dev.js中最易被忽略但影响巨大的 5 个细节const path require(path); const webpack require(webpack); const { merge } require(webpack-merge); const baseConfig require(./webpack.base.js); // 1. devServer本地服务配置proxy 是跨域调试核心 const devServer { // port: 8080 是默认端口但建议显式声明避免端口冲突时无提示 port: 8080, // host: 0.0.0.0 允许局域网访问方便手机真机调试 host: 0.0.0.0, // open: true 自动打开浏览器省去手动输入地址步骤 open: true, // hot: true 启用 HMR但必须配合 HotModuleReplacementPlugin hot: true, // overlay: true 编译错误时在浏览器全屏显示避免只看 console overlay: true, // proxy后端接口代理解决跨域 // 注意target 必须是完整 URL含 http://且 changeOrigin: true 修改 origin 头 proxy: { /api: { target: http://localhost:3000, changeOrigin: true, // pathRewrite: {^/api: } 表示请求 /api/user → 代理到 /user pathRewrite: { ^/api: } } } }; // 2. devtoolsource-map 类型选择平衡速度与调试精度 // eval-source-map最快但每行映射到原始文件适合开发 // cheap-module-eval-source-map忽略 loader 生成的列信息速度更快推荐 const devtool cheap-module-eval-source-map; // 3. module.rules 覆盖将 CSS rule 中的 style-loader 替换为热更新版本 // 注意不能直接写 style-loader必须用 { loader: style-loader, options: { hmr: true } } const cssRuleOverride { test: /\.css$/, use: [ { loader: style-loader, options: { // hmr: true 显式启用热更新否则部分场景失效 hmr: true, // injectType: styleTag 表示插入 style 标签而非 link injectType: styleTag } }, { loader: css-loader, options: { modules: false, importLoaders: 1 } }, postcss-loader ] }; // 4. plugins开发专用插件 const plugins [ // HotModuleReplacementPluginHMR 核心插件Webpack 4.x 必须手动注入 // 即使 devServer.hot true没有此插件 HMR 也不会工作 new webpack.HotModuleReplacementPlugin(), // NamedModulesPlugin开发时显示模块完整路径如 ./src/utils/request.js便于定位 // Webpack 4.x 中 mode: development 默认启用但显式声明更可控 new webpack.NamedModulesPlugin() ]; // 5. performance关闭性能警告避免 console 刷屏 // Webpack 4.x 默认对 250KB 的 chunk 发出警告开发时无意义 const performance { hints: false }; module.exports merge(baseConfig, { mode: development, devtool, devServer, // 使用 merge.smartStrategy 覆盖 module.rules module: { rules: [cssRuleOverride] }, plugins, performance });注意事项devServer.proxy的changeOrigin: true是跨域调试的生命线。若不启用后端收到的Origin请求头仍是http://localhost:8080而 CORS 策略检查的是Origin导致 403 错误。我曾在一个政府项目中因忘记此配置浪费 3 小时排查“后端明明开了 CORS 为何还报错”。3.3 webpack.prod.js生产构建的 4 个体积杀手与对策生产配置的目标是“小”与“稳”体积小、加载快、错误少、部署稳。以下是 Webpack 4.x 中最常被低估的 4 个体积膨胀点及对应解法const path require(path); const webpack require(webpack); const MiniCssExtractPlugin require(mini-css-extract-plugin); const TerserPlugin require(terser-webpack-plugin); const { CleanWebpackPlugin } require(clean-webpack-plugin); const HtmlWebpackPlugin require(html-webpack-plugin); const { merge } require(webpack-merge); const baseConfig require(./webpack.base.js); // 1. devtool生产环境 source-map 必须谨慎 // hidden-source-map生成 map 文件但不关联到 bundle避免泄露源码路径 // nosources-source-map仅包含错误位置不包含源码内容最安全 const devtool hidden-source-map; // 2. module.rules 覆盖CSS 提取为独立文件 const cssRuleOverride { test: /\.css$/, use: [ // 替换为 MiniCssExtractPlugin.loader它将 CSS 提取为 .css 文件 MiniCssExtractPlugin.loader, { loader: css-loader, options: { modules: false, importLoaders: 1 } }, postcss-loader ] }; // 3. plugins生产专用插件 const plugins [ // CleanWebpackPlugin构建前清空 dist 目录避免旧文件残留 // 注意new CleanWebpackPlugin() 无参数即可v3 版本默认清空 output.path new CleanWebpackPlugin(), // MiniCssExtractPlugin提取 CSSfilename 必须与 output.filename 同级 new MiniCssExtractPlugin({ filename: css/[name].[contenthash:8].css, chunkFilename: css/[name].[contenthash:8].css }), // HtmlWebpackPlugin生成 HTML关键配置 chunks 控制 script 加载顺序 new HtmlWebpackPlugin({ template: ./public/index.html, // chunks: [vendors, app] 确保 vendors.js 在 app.js 之前加载 chunks: [vendors, app], // minifyHTML 压缩移除空格、注释等 minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true } }), // DefinePlugin生产环境注入 NODE_ENV影响 React/Vue 的 dev 模式代码剔除 new webpack.DefinePlugin({ process.env.NODE_ENV: JSON.stringify(production) }) ]; // 4. optimization终极体积优化 const optimization { // splitChunks细化 vendor 分组避免 moment.js 等大库污染 vendors splitChunks: { chunks: all, minSize: 30000, maxSize: 250000, cacheGroups: { // antd 组单独提取 antd避免其样式与业务 CSS 混合 antd: { name: antd, test: /[\\/]node_modules[\\/](antd|ant-design)[\\/]/, priority: 20, reuseExistingChunk: true }, // moment 组moment 体积大且 locale 文件多单独提取 moment: { name: moment, test: /[\\/]node_modules[\\/](moment|moment-timezone)[\\/]/, priority: 15, reuseExistingChunk: true }, // vendor 组兜底匹配其他 node_modules vendor: { name: vendors, test: /[\\/]node_modules[\\/]/, priority: 10, reuseExistingChunk: true } } }, // minimizer自定义压缩器TerserPlugin 支持 ES2020 语法 minimizer: [ new TerserPlugin({ terserOptions: { compress: { // drop_console: true 移除所有 console减小体积 drop_console: true, // drop_debugger: true 移除 debugger 语句 drop_debugger: true, // pure_funcs: [console.log] 指定函数调用可安全移除 pure_funcs: [console.log, console.warn] }, format: { // comments: false 移除注释 comments: false } } }) ] }; module.exports merge(baseConfig, { mode: production, devtool, module: { rules: [cssRuleOverride] }, plugins, optimization });实操心得splitChunks.cacheGroups的priority是体积控制的灵魂。若不设 priorityWebpack 4.x 会按 cacheGroups 对象属性顺序匹配而 JavaScript 对象属性遍历顺序不可靠V8 引擎下数字键优先字符串键按插入顺序。因此必须显式设 priority确保antd组在vendor组之前匹配否则 antd 的 CSS 会混入vendors.css导致样式污染。我曾因此问题让一个 2MB 的vendors.css无法被 CDN 缓存CDN 命中率从 95% 降至 40%。4. 常见问题与排查技巧实录4.1 构建报错Module not found: Error: Cant resolve xxx这是 Webpack 4.x 最高频报错表面是路径问题根源常是resolve.alias或resolve.modules配置失误。以下是典型场景与解法场景错误示例根本原因解决方案alias路径错误Cant resolve utils/request别名指向src/但utils/request实际路径是src/utils/request.jsutils并不存在检查resolve.alias中的路径是否为path.resolve(__dirname, ../src)且src目录下确有utils子目录modules未包含node_modulesCant resolve reactresolve.modules被误设为[src, node_modules]但 Webpack 4.x 默认不包含node_modules必须显式添加在resolve.modules中加入path.resolve(__dirname, ../node_modules)或直接删除该配置使用默认值exports字段解析失败Cant resolve lodash-eslodash-es的package.json中exports字段 Webpack 4.x 不支持需降级或改用lodash将lodash-es降级至v4.17.21最后支持 4.x 的版本或改用lodash并按需引入import debounce from lodash/debounce排查技巧在报错信息中找到resolve关键字然后运行npx webpack --config webpack.base.js --display-modules查看 Webpack 实际解析路径。若路径显示.../node_modules/react/index.js说明解析成功若显示.../src/react/index.js说明resolve.modules顺序错误src优先于node_modules。4.2 热更新失效HMR 不触发或样式不更新HMR 失效常被归咎于配置实则 80% 是代码写法问题。以下是真实案例场景1React 函数组件未导出默认函数错误写法// src/App.js function App() { return divHello/div; } export default App; // ✅ 正确 // 但若写成 export const App () divHello/div; ❌ HMR 失效原因export const创建的是命名导出Webpack HMR 无法追踪其更新。必须用export default。场景2CSS Modules 未启用hot: true错误配置{ loader: css-loader, options: { modules: true } // 缺少 hot: true }正确配置{ loader: css-loader, options: { modules: true, // 必须加此行否则 CSS Modules 的 HMR 不工作 hot: true } }场景3Vue 单文件组件中style未加scoped无scoped的样式会全局注入HMR 更新时旧样式未清除导致样式叠加。解决方案所有style标签加scoped或使用vue-style-loader替代style-loader。实操心得HMR 调试口诀——“看控制台、查 network、断点 update”。首先看浏览器控制台是否打印App is up to date若无则 HMR 未触发其次打开 Network 面板修改文件后观察是否有hot-update.json请求最后在node_modules/webpack/hot/dev-server.js第 50 行打 debugger看check函数是否执行。4.3 打包体积异常dist目录过大或文件缺失体积问题常源于optimization.splitChunks配置不当或externals误用。以下是两个经典案例案例1vendors.js体积达 5MB原因splitChunks.cacheGroups.vendor.test正则过于宽泛匹配了node_modules下所有文件包括docs/、test/等非代码目录。解法收紧正则改为test: /[\\/]node_modules[\\/][^\\/][\\/]/确保只匹配二级目录如react/、lodash/排除node_modules/react/docs/。**