Skip to content

Webpack优化

性能分析

Stats 输出

通过 stats 配置详细输出构建信息:

js
module.exports = {
  stats: {
    assets: true,       // 显示资源信息
    modules: true,      // 显示模块信息
    moduleTrace: true,  // 显示模块间的依赖关系
    chunks: true,       // 显示 chunk 信息
    reasons: true,      // 显示模块被引入的原因
    timings: true,      // 显示构建耗时
  }
};

使用命令行参数 --json 导出完整构建数据到文件:

shell
webpack --json > stats.json

Performance 选项

设置性能提示阈值,超过时警告或错误:

js
module.exports = {
  performance: {
    hints: 'warning',           // 或 'error'、false
    maxAssetSize: 200000,       // 单个资源大小限制(字节)
    maxEntrypointSize: 400000,  // 入口资源大小限制
  }
};

webpack-bundle-analyzer 大小分析

使用 webpack-bundle-analyzer 插件可视化分析打包结果:

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

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',  // 生成 HTML 文件
      openAnalyzer: false,     // 不自动打开
    })
  ]
};

speed-measure-webpack-plugin 速度分析

测量每个 loader 和 plugin 的执行耗时:

js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // 原始 webpack 配置
});

loader性能分析

在 loader 配置中添加 profile: true 记录耗时:

js
{
  test: /\.js$/,
  use: {
    loader: 'babel-loader',
    options: {
      profile: true
    }
  }
}

持续集成

每次构建完成,将 stats.json 上传到持续集成服务器

shell
webpack --json > stats.json && aws s3 cp stats.json s3://my-bucket/reports/

速度优化

开启多线程

  1. 使用 thread-loader 在构建过程中开启多线程:
js
{
  test: /\.js$/,
  use: {
    loader: 'thread-loader',
    options: {
      workers: 2 // 设置线程数
    }
  }
}
  1. happypack(webpack 4 及以下版本)

缓存机制

  1. webpack 5的持久化缓存(cache 参数)
js
module.exports = {
  cache: {
    type: 'filesystem', // 使用文件系统缓存
    buildDependencies: {
      config: [__filename] // 当配置文件变化时重新缓存
    }
  }
};
  1. 为loader开启缓存
js
{
  test: /\.js$/,
  use: {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true // 开启缓存
    }
  }
}

缩小构建范围

  1. 精准配置 loader 作用范围,使用 include/exclude 避免处理无关文件:
js
{
  test: /\.js$/,
  include: path.resolve(__dirname, 'src'), // 只处理 src 目录
  exclude: /node_modules/,
  use: 'babel-loader'
}
  1. 优化 resolve 配置
  • 减少 resolve.modules 搜索路径
  • 使用 resolve.alias 缩短模块查找路径
  • 合理配置 resolve.extensions(减少扩展名尝试次数)
js
module.exports = {
  resolve: {
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
    extensions: ['.js', '.ts', '.json'] // 按频率排序
  }
};
  1. noParse,跳过对无需解析的模块(如 jQuery、lodash)的解析:
js
module.exports = {
  module: {
    noParse: /jquery|lodash/
  }
};

开发环境优化

  1. 使用 mode: 'development' 开启开发模式,自动启用一些优化选项:

    • 开发环境友好的代码生成,如不压缩代码,保留注释,保留原始变量名
    • 默认开启source-map: devtool: 'eval'
    • 开启热更新:devServer:
    • 关闭tree-shaking
    • 内联css
  2. 合适的source-map:使用 devtool: 'eval-source-map' 提高构建速度,便于调试

生产环境优化

  1. 使用 mode: 'production' 开启生产模式,自动启用代码压缩、tree-shaking 等优化。
  2. DllPlugin, 预编译第三方库,减少主应用的构建时间:
js
// webpack.dll.js
const webpack = require('webpack');

module.exports = {
  entry: {
    vendor: ['react', 'react-dom']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'dist/dll'),
    library: '[name]_[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_[hash]',
      path: path.resolve(__dirname, 'dist/dll/[name]-manifest.json')
    })
  ]
};

在主配置中引入 DllReferencePlugin:

js
const webpack = require('webpack');

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

体积优化

Tree-shaking

  • 前提条件:
    • 使用 ES6 模块语法(import/export),而非 CommonJS(require)。
    • 在 package.json 中添加 "sideEffects": false(或指定无副作用的文件)。
js
// webpack.config.js
module.exports = {
  mode: 'production', // 生产模式自动启用 Tree Shaking
  optimization: {
    usedExports: true, // 标记未使用的导出
    minimize: true,    // 压缩代码时移除未使用的导出
  }
};

代码分割

1. 动态导入 (Dynamic Import)

通过异步加载实现懒加载(如路由级分割):

js
// 同步加载(全部打包到一个文件)
import { add } from './math';

// 异步加载(按需分割成独立 chunk)
button.addEventListener('click', () => {
  import('./math').then(math => {
    console.log(math.add(1, 2));
  });
});

2. SplitChunksPlugin

配置公共模块拆分,避免重复打包:

js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',        // 分割所有类型的 chunk
      minSize: 20000,       // 最小分割体积(字节)
      cacheGroups: {
        vendor: {           // 分割第三方库
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        },
        common: {           // 分割公共模块
          minChunks: 2,
          name: 'common'
        }
      }
    }
  }
};

代码压缩

1. JS压缩:terser-webpack-plugin(webpack5自带)

js
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 移除 console.log
            dead_code: true     // 移除死代码
          },
          mangle: true          // 混淆变量名
        }
      })
    ]
  }
};

2. CSS压缩:css-minimizer-webpack-plugin

js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin()
    ]
  }
};

3. 资源压缩

  1. 图片压缩:使用 image-minimizer-webpack-plugin:
  2. 小资源内联:使用 url-loader 或自定义 loader 将小于指定大小的资源内联为 base64:
  3. 标记移除未使用的CSS:使用 purgecss-webpack-plugin 移除未使用的 CSS:
  4. 优先引用支持 Tree Shaking 的 ESModule 版本(如 lodash-es 替代 lodash)。

4. CDN 加载外部资源(externals配置)

通过 externals 排除第三方库,改用 CDN 加载,如 jQuery、React 等:

js
// webpack.config.js
module.exports = {
  externals: {
    react: 'React',           // 从全局变量 React 中获取
    'react-dom': 'ReactDOM'   // 从全局变量 ReactDOM 中获取
  }
};

// index.html
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"></script>

但有一定的风险,

  • 稳定性:CDN加载失败可能性
  • 调试成本:与本地开发环境具有差异,缺少source-map
  • 安全风险

其他优化角度

  1. 利用浏览器缓存,使用contenthash 生成文件名,避免浏览器缓存问题。
  2. 使用 动态导入,按需加载模块,减少初始加载体积。