Webpack 构建优化

Webpack 构建速度与打包体积优化方案

问题

如何优化 Webpack 的构建速度和打包体积?涉及 Tree Shaking、SplitChunks、DllPlugin、并行构建、缓存等技术。

解答

1. Tree Shaking

移除未使用的代码,需要 ES Module 语法支持。

// webpack.config.js
module.exports = {
  mode: 'production', // 生产模式自动开启
  optimization: {
    usedExports: true, // 标记未使用的导出
    minimize: true,    // 压缩时移除未使用代码
  },
};

// package.json - 标记无副作用的模块
{
  "sideEffects": false
  // 或指定有副作用的文件
  // "sideEffects": ["*.css", "./src/polyfill.js"]
}
// utils.js
export function used() {
  return 'I am used';
}

export function unused() {
  return 'I will be removed';
}

// main.js
import { used } from './utils';
console.log(used()); // unused 会被 Tree Shaking 移除

2. SplitChunks 代码分割

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有类型的 chunk 生效
      minSize: 20000, // 最小 20kb 才分割
      minChunks: 1,   // 至少被引用 1 次
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      cacheGroups: {
        // 第三方库单独打包
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
        },
        // 公共模块
        common: {
          minChunks: 2, // 至少被 2 个 chunk 引用
          name: 'common',
          priority: 5,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

3. DllPlugin 预编译

将不常变动的库预先打包,加速后续构建。

// webpack.dll.config.js - 预编译配置
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    vendor: ['react', 'react-dom', 'lodash'], // 要预编译的库
  },
  output: {
    path: path.resolve(__dirname, 'dll'),
    filename: '[name].dll.js',
    library: '[name]_library',
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_library',
      path: path.resolve(__dirname, 'dll/[name].manifest.json'),
    }),
  ],
};

// webpack.config.js - 主配置引用 DLL
const webpack = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      manifest: require('./dll/vendor.manifest.json'),
    }),
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, 'dll/vendor.dll.js'),
    }),
  ],
};
# 先执行 DLL 构建(只需执行一次)
webpack --config webpack.dll.config.js
# 再执行正常构建
webpack

4. 并行构建

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'thread-loader', // 多线程处理
            options: {
              workers: 4, // worker 数量
            },
          },
          'babel-loader',
        ],
      },
    ],
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true, // 并行压缩 JS
      }),
      new CssMinimizerPlugin({
        parallel: true, // 并行压缩 CSS
      }),
    ],
  },
};

5. 缓存策略

// webpack.config.js
const path = require('path');

module.exports = {
  // 持久化缓存(Webpack 5)
  cache: {
    type: 'filesystem', // 文件系统缓存
    cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
    buildDependencies: {
      config: [__filename], // 配置文件变化时使缓存失效
    },
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true, // babel-loader 缓存
          },
        },
      },
    ],
  },
  output: {
    // 内容哈希用于浏览器缓存
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
  },
};

6. 其他优化

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  // 缩小查找范围
  resolve: {
    extensions: ['.js', '.jsx'], // 减少扩展名尝试
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
    modules: [path.resolve(__dirname, 'node_modules')], // 指定模块目录
  },
  // 排除不需要解析的库
  module: {
    noParse: /jquery|lodash/, // 这些库不需要解析依赖
  },
  // 外部依赖(CDN 引入)
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
  plugins: [
    // 分析打包结果
    new BundleAnalyzerPlugin(),
  ],
};

关键点

  • Tree Shaking:依赖 ES Module 静态分析,需配置 sideEffects 标记无副作用模块
  • SplitChunks:按策略分割代码,分离第三方库和公共模块,利用浏览器缓存
  • DllPlugin:预编译不常变动的库,避免重复构建,Webpack 5 中可用持久化缓存替代
  • 并行构建thread-loader 多线程编译,TerserPlugin 并行压缩
  • 缓存:Webpack 5 的 filesystem 缓存、babel-loader 缓存、contenthash 浏览器缓存
  • 缩小范围resolve.modulesnoParseexternals 减少不必要的处理