You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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配置修改为:
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(isDevelopment ? 'development' : 'production'),
      PRODUCTION: JSON.stringify(!isDevelopment)
    })
    
    该修改通常可直接减少30%-60%的产物体积。
  • 按环境区分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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 00:21:14