Webpack 4 构建优化篇

随着业务迭代,项目页面数量增加、第三方依赖不断引入,Webpack 会出现两大典型问题:

  • 构建编译速度慢(开发环境热更新编译耗时长)
  • 打包产物体积臃肿(线上资源加载慢,影响用户体验)

本文以 Webpack 4 为主线,区分构建速度优化产物体积优化,配套分析工具、可直接复制的配置与实际踩坑经验。

性能分析工具

优化不能靠主观猜测,先采集数据:包体积分析编译耗时分析,定位真正瓶颈。

webpack-bundle-analyzer 包体积可视化分析

交互式树状图,直观展示每个模块占 bundle 的大小,快速定位体积过大的第三方库、重复依赖。

npm install webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'server' // 启动web服务打开分析页面;可选 static 输出静态html文件
    })
  ]
};

也可以通过环境变量控制开关;配置里根据 process.env.npm_config_report 决定是否加入插件,例如:

plugins: process.env.npm_config_report
  ? [new BundleAnalyzerPlugin()]
  : []
NODE_ENV=production npm_config_report=true npm run build

webpack-bundle-analyzer

speed-measure-webpack-plugin 编译耗时统计

统计每一个 loaderplugin 的执行耗时,找到拖慢编译的环节。

npm install speed-measure-webpack-plugin -D
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

// 使用 wrap 包裹完整 webpack 配置
const webpackConfig = smp.wrap({
  // 原有完整配置写在这里
  plugins: []
});

module.exports = webpackConfig;

webpack 内置 --profile 参数,无需插件也可以输出模块耗时日志:

npm run build -- --profile

speed-measure-webpack-plugin

速度优化

多页面项目热更新优化

Webpack 4 的 html-webpack-plugin(v4+)原生自带缓存机制,多页面项目直接多次实例化即可,不需要第三方补丁插件。

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
      inject: true,
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      filename: 'list.html',
      template: './src/list.html',
      inject: true,
      chunks: ['list']
    })
  ]
};

babel-loader 开启缓存

babel-loader 开启 cacheDirectory,将转译结果缓存到磁盘,二次编译跳过未修改文件,显著提升开发编译速度。

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true // 开启磁盘缓存
          }
        }
      }
    ]
  }
};

合理配置 devServer

开发服务器合理配置,减少不必要文件监听、编译开销:

// webpack.dev.js
module.exports = {
  devServer: {
    hot: true, // 开启HMR热模块替换
    watchOptions: {
      ignored: /node_modules/ // 忽略node_modules文件监听,减少文件观察数量
    }
  }
};

备注:这是 webpack-dev-server 3(配合 Webpack 4)的写法;webpack-dev-server 4+ 改为 watchFilesstatic.watch

DllPlugin 预编译静态第三方库(开发环境提速)

DllPlugin 属于开发环境优化手段,生产环境优先使用 splitChunks 做代码分割。

AutoDllPlugin 第三方插件可以简化配置,但存在一些边界兼容问题,推荐直接使用 webpack 原生 DllPlugin + DllReferencePlugin

新建 webpack.dll.config.js,预编译不常改动的第三方库:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    vendor: ['vue/dist/vue.esm.js', 'axios', 'lodash']
  },
  output: {
    path: path.resolve(__dirname, 'dll'),
    filename: '[name].dll.js',
    library: '[name]_dll'
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.resolve(__dirname, 'dll', '[name]-manifest.json'),
      name: '[name]_dll',
      context: __dirname
    })
  ]
};

执行预编译,生成 dll 文件与清单:

webpack --config webpack.dll.config.js

在业务 webpack.dev.js 使用 DllReferencePlugin 读取清单,跳过这些库的编译。

const webpack = require('webpack');
const path = require('path');

module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require(path.resolve(__dirname, 'dll/vendor-manifest.json'))
    })
  ]
};

在 HTML 模板手动引入生成的 vendor.dll.js

<script src="./dll/vendor.dll.js"></script>

备注:只有新增、升级第三方依赖包时,才需要重新执行 dll 打包;日常业务代码修改,不需要重新打包 dll。

体积优化

Webpack 4 内置 SplitChunksPlugin,不需要手动 new 插件实例,直接在 optimization.splitChunks 配置。

作用:提取重复的业务公共模块、node_modules 第三方库,拆分为独立 chunk,利用浏览器长效缓存。

// webpack.prod.js
module.exports = {
  mode: 'production',
  optimization: {
    // 把 webpack 的 runtime 运行时代码抽离为独立文件,利于缓存
    runtimeChunk: true,
    splitChunks: {
      /**
       * chunks
       * async:仅分割异步import()的chunk(默认)
       * initial:仅分割同步引入的chunk
       * all:同步 + 异步全部分割,多页面项目推荐 all
       */
      chunks: 'all',
      minSize: 30000, // 模块大于30kb才会单独拆分(压缩前大小)
      maxSize: 0,
      minChunks: 1, // 模块至少被1个chunk引用才拆分
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      // cacheGroups 缓存组,核心配置,可以覆盖外层参数
      cacheGroups: {
        // 提取 node_modules 第三方库
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        // 业务公共模块,被至少2个chunk引用才抽离
        common: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true // 复用已经存在的chunk,不重复创建
        }
      }
    }
  }
};

cacheGroups 关键字段说明:

  • test:匹配模块路径,满足条件归入该缓存组;
  • priority:优先级,数字越大优先级越高,一个模块匹配多个缓存组时归优先级最高的;
  • reuseExistingChunk:如果 chunk 已经存在,则直接复用,不再生成新文件。

备注name: true 是 Webpack 4 的写法,表示由 Webpack 自动生成 chunk 名;Webpack 5 不再接受布尔值,需改成字符串、函数或 false

代码压缩

Webpack 4 在 mode: 'production' 时默认开启代码压缩;也可以手动配置 terser-webpack-plugin,自定义压缩行为。

npm install terser-webpack-plugin -D
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true, // 开启多进程并行压缩,充分利用多核CPU
        terserOptions: {
          compress: {
            drop_console: true // 删除console,可选
          }
        },
        sourceMap: false
      })
    ]
  }
};

备注:插件顶层的 sourceMap 是 Webpack 4 所用 terser-webpack-plugin 的写法;较新版本已移除该选项,改为跟随 webpack 的 devtool

将大型第三方库不参与打包,HTML 通过 CDN 引入,业务代码正常 import,webpack 将该模块从 bundle 中排除,直接减小产物体积。

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
module.exports = {
  externals: {
    vue: 'Vue' // import vue 映射到全局变量 Vue
  }
};
import Vue from 'vue';

resolve 模块解析优化

减少模块查找开销,合理设置别名、检索后缀、检索目录。

const path = require('path');
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
    extensions: ['.js', '.vue', '.json'], // 只写项目实际用到的后缀,不要写一大堆
    modules: [path.resolve(__dirname, 'node_modules')] // 限定模块查找目录
  }
};

devtool source-map 合理配置

source-map 会增加编译耗时与产物大小,开发、生产环境分开配置。

关键字含义:

  • eval:eval 包裹模块代码
  • source-map:输出独立 .map 文件
  • cheap:不包含列信息
  • module:保留 loader 转换前源码
  • inline:map 嵌入 bundle,不生成独立文件
// webpack.dev.js 开发环境,便于调试
module.exports = {
  devtool: 'cheap-module-eval-source-map'
};

// webpack.prod.js 生产环境
module.exports = {
  // devtool: 'source-map' // 需要调试线上报错时开启,单独输出map,部署可以不上传map
  devtool: false // 关闭source-map,生产追求速度和体积优先
};

备注cheap-module-eval-source-map 是 Webpack 4 的名称;Webpack 5 改为 eval-cheap-module-source-map

其他体积优化

MiniCssExtractPlugin 提取 CSS

生产环境不要用 style-loader,使用 MiniCssExtractPlugin 将 CSS 提取为独立 CSS 文件,配合 splitChunks 还可以拆分公共 CSS。

npm install mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[name].[contenthash:8].chunk.css'
    })
  ],
  optimization: {
    minimizer: [
      // Webpack 4 写入 minimizer 会覆盖默认压缩器,JS 压缩需要自己带上
      new TerserPlugin({
        parallel: true,
        sourceMap: false
      }),
      new OptimizeCSSAssetsPlugin()
    ]
  }
};

备注:Webpack 5 改用 css-minimizer-webpack-plugin,并可用 '...' 保留内置 TerserPlugin。

DefinePlugin 注入环境变量

Webpack 4 在 mode: 'production' 时会自动设置 process.env.NODE_ENV;也可以手动使用 DefinePlugin,配合 tree-shaking 剔除死代码。

const webpack = require('webpack');
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
};

合理使用 tree-shaking

tree-shaking 用来剔除未使用 ES Module 代码;要求:

  • 使用 ES6 importexport不使用 CommonJS require
  • mode: 'production'
  • babel 不要把 ES Module 转成 CommonJS(@babel/preset-env 设置 modules: false,交给 Webpack 处理模块);
  • 依赖包的 package.json 正确声明 sideEffects,否则有副作用的模块不容易被剔除。

总结

  • 废弃清单(不要再使用)
    • CommonsChunkPlugin:Webpack 4 已移除,替换为 splitChunks
    • html-webpack-plugin-for-multihtml:仅 Webpack 3,Webpack 4 原生 html-webpack-plugin 自带缓存
    • increase-memory-limit:废弃,改用 NODE_OPTIONS=--max-old-space-size=4096
    • webpack-parallel-uglify-plugin:废弃,Webpack 4 内置 TerserPlugin 支持并行
    • HappyPack:停止维护,优先使用 thread-loader
  • DllPlugin 定位:主要用于开发环境提速;生产环境优先用 splitChunks 做代码分割,不要把 dll 产物直接作为线上资源。
  • splitChunks.chunks: 'all':多页面项目建议设置为 all,同步、异步模块都参与分割,更充分提取公共代码。
  • 开发环境优先:babel-loadercacheDirectory + devServer 忽略 node_modules + DllPlugin(大型项目);生产环境优先:splitChunks + TerserPlugin + MiniCssExtractPlugin + externals。

参考资料

Webpack 4 官方文档

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-25 21:00:45

results matching ""

    No results matching ""