Webpack4生产模式构建chunk体积大于开发模式如何优化
Webpack 4生产构建chunk体积异常偏大优化方案
问题背景
使用Webpack 4分别执行开发、生产模式构建时,出现生产模式产出chunk体积大于开发模式的异常现象:
- 开发模式下对应组件stat size为203KB

- 相同代码生产模式构建时,同一组件stat size达到676KB

移除optimization配置后体积异常无变化,可排除优化配置本身的影响。当前使用的Webpack配置如下:
const webpack = require('webpack'); const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const DuplicatePackageCheckerPlugin = require('@cerner/duplicate-package-checker-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const bundleAnalyzerPlugin = new BundleAnalyzerPlugin(); /** ignore @anwb packages * Keep warning for multiple react-is versions as warning * * */ const duplicatePackageChecker = new DuplicatePackageCheckerPlugin({ verbose: true, exclude(instance) { // ignore @anwb packages if (instance.name.includes('@anwb')) { return true; } return false; }, }); /** clean dist on build , webpack 5 supports clean property in output object * */ const cleanWebpackPlugin = new CleanWebpackPlugin(); module.exports = (env, argv) => { const isDevelopment = argv.mode === 'development'; // No sourcemaps in production // const devtool = isDevelopment ? { devtool: 'source-map' } : {}; const devtool = { devtool: 'source-map' }; // Separated output folders for OTA an P to separate artifacts in pipeline const distFolder = isDevelopment ? 'distota' : 'dist'; console.log(`Webpack build for : ${isDevelopment ? 'development' : 'production'}`); console.log(`Source maps included : ${Object.keys(devtool).length !== 0 ? 'yes' : 'no'}`); console.log(`Build directory : ${distFolder}`); return { entry: { index: path.resolve(__dirname, './src/index.tsx'), }, /** * Library target is set to umd so the anwb core js loading system can import it */ output: { path: path.resolve(__dirname, `./${distFolder}`), filename: '[name].js', chunkFilename: '[name].js', libraryTarget: 'umd', publicPath: `/`, }, resolve: { /** * Import files from the src or node_modules directory */ modules: ['src', 'node_modules'], /** * Load preact-compat when react is encountered */ alias: { react: 'preact/compat', 'react-dom': 'preact/compat', '@anwb/typography': path.resolve(__dirname, '.', 'node_modules', '@anwb/typography'), }, extensions: ['.ts', '.tsx', '.js'], }, bail: true, /** * Include source maps */ ...devtool, module: { rules: [ /** * The font is loaded using the fileloader and placed in a fonts folder */ { test: /\.(eot|ttf|woff|woff2)$/, loader: 'file-loader', options: { name: 'fonts/[name].[ext]', }, }, /** * The svg images are loaded and placed in the images directory */ { test: /\.svg$/, loader: 'file-loader', options: { name: 'images/[name][hash].[ext]', }, }, /** * All images bigger then 25000 bytes are placed in an images folder. Small images are included as inline base 64. */ { test: /\.(png|jpg|gif)$/, loader: 'url-loader', options: { limit: '25000', name: 'images/[name][hash].[ext]', }, }, /** * All less files are converted down to css and are autoprefixed using the postcss module */ { test: /\.(less|css)$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', { loader: 'less-loader', options: { lessOptions: { javascriptEnabled: true, }, }, }, ], }, /** * Js is transpiled using babel */ { test: /\.(js|ts|tsx)$/, exclude: ['/node_modules'], include: [/src/, /node_modules\/@anwb/], loader: 'babel-loader', options: { presets: [ [ '@babel/env', { modules: false, targets: ['> 1% in NL', 'last 2 versions', 'last 5 iOS versions'], // NewRelic uses Chrome 72 (2019). Therefore we need to Polyfill some features like Promise.allSettled. // Story is made to remove it when NR is updated (MACS-13942) useBuiltIns: 'usage', corejs: { version: 3, proposals: true }, }, ], '@babel/react', '@babel/flow', ], plugins: [ ['@babel/plugin-transform-react-jsx', { pragma: 'h' }], 'syntax-dynamic-import', ], }, }, ], }, optimization: { // splitChunks: { // chunks: 'all', // }, minimizer: [ new CssMinimizerPlugin({ test: /.css$/i, minimizerOptions: { preset: [ 'default', { discardComments: { removeAll: true }, }, ], }, }), new UglifyJsPlugin({ uglifyOptions: { output: { comments: false, }, }, }), ], }, plugins: [ bundleAnalyzerPlugin, cleanWebpackPlugin, /** * The extract text plugin makes sure that all css is put into a single css file named after the application */ new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].css', }), duplicatePackageChecker, /** * Dont use all moment locales */ new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /nl/), new webpack.DefinePlugin({ // "process.env": { // NODE_ENV: simulateProduction ? JSON.stringify("production") : JSON.stringify("development") // }, PRODUCTION: true, }), ], }; };
项目构建脚本配置如下:
"build": "webpack --config ./webpack.js --mode=production", "build:ota": "webpack --config ./webpack.js --mode=development",
核心优化方案
一、修复配置错误导致的体积异常
- 正确注入生产环境标识:现有配置注释掉了
process.env.NODE_ENV的赋值,会导致React/Preact、各类第三方库无法自动剔除开发环境的debug日志、类型校验、警告提示等冗余代码,这是体积暴涨的核心原因。将DefinePlugin配置修改为:
该修改通常可直接减少30%-60%的产物体积。new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(isDevelopment ? 'development' : 'production'), PRODUCTION: JSON.stringify(!isDevelopment) }) - 按环境区分sourcemap配置:现有配置强制所有环境开启
source-map模式,该模式会生成完整的源码映射,大幅提升stat size统计值;而开发模式默认的eval类sourcemap不会生成全量映射,因此统计值更小。修改devtool配置:// 需要线上排查错误时用hidden-source-map,仅生成独立map文件不注入js引用 const devtool = isDevelopment ? { devtool: 'eval-cheap-module-source-map' } : { devtool: 'hidden-source-map' }; // 不需要线上排查可直接关闭生产环境sourcemap,体积可再降30% // const devtool = isDevelopment ? { devtool: 'eval-cheap-module-source-map' } : {}; - 替换不兼容ES6+的压缩插件:现有使用的
uglifyjs-webpack-plugin仅支持ES5语法压缩,无法处理项目中ES6+代码和core-js注入的polyfill,导致生产模式下大量代码未被压缩。替换为Webpack 4兼容的terser-webpack-plugin@4版本,配置如下:const TerserPlugin = require('terser-webpack-plugin'); // 加入optimization.minimizer数组 new TerserPlugin({ terserOptions: { compress: { drop_console: true, drop_debugger: true }, format: { comments: false } }, extractComments: false }) - 修正babel-loader的exclude规则:现有配置写的
exclude: ['/node_modules']是字符串数组,Webpack无法识别为路径匹配规则,实际不生效,会导致babel编译大量不需要处理的第三方依赖代码。修改为正则匹配:exclude: /node_modules/, include: [/src/, /node_modules\/@anwb/],
二、开启Webpack内置优化能力
- 启用splitChunks代码分割:现有配置注释掉了splitChunks规则,所有第三方依赖、公共代码、业务代码全部打包到同一个chunk,导致单文件体积过大。开启分割规则:
optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: -10 }, common: { minChunks: 2, name: 'common', priority: -20, reuseExistingChunk: true } } }, concatenateModules: !isDevelopment, // 开启作用域提升,减少闭包冗余代码 usedExports: !isDevelopment, // 开启Tree Shaking,移除未引用代码 minimizer: [/* 上述CSS、JS压缩插件 */] } - 生产环境关闭BundleAnalyzerPlugin:该插件会注入体积分析用的辅助代码,增加产物体积,仅在需要分析时手动开启即可:
plugins: [ isDevelopment ? bundleAnalyzerPlugin : null, cleanWebpackPlugin, // 其余插件 ].filter(Boolean) - 关闭不必要的polyfill注入:babel配置中
corejs.proposals = true会注入大量未进入标准的提案API polyfill,如无明确需求可设为false,减少polyfill体积。
三、依赖层面优化
- 移除
@anwb/typography的强制路径alias,避免因路径解析问题导致重复打包依赖,配合DuplicatePackageCheckerPlugin的提示清理重复依赖。 - 生产环境加入
babel-plugin-transform-react-remove-prop-types插件,自动移除Preact/React组件的PropTypes校验代码。 - 如无强需求,可将moment替换为dayjs,体积可减少80%以上。
内容的提问来源于stack exchange,提问作者pcvnes
相关产品推荐
相关产品推荐

