Webpack打包优化实战:5个提升构建速度的关键技巧

端木依珂 优化 阅读 2,039
赞 16 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

我最近接手一个老项目,打包时间从 45 秒飙到 2 分钟,本地开发时改一行 CSS 都要等半分钟,简直没法干活。折腾了几天,最后把 Webpack 配置重构成一套亲测有效的优化方案,现在冷启动 18 秒,热更新基本秒出。下面直接上干货,不讲理论,只讲我实际用的、能跑起来的配置。

Webpack打包优化实战:5个提升构建速度的关键技巧

核心代码就这几行

别被网上那些“十大优化技巧”吓到,真正见效的就那么几个。我整理了自己项目里最核心的三块配置,直接复制粘贴就能用。

首先是 cache,Webpack 5 原生支持持久化缓存,打开它几乎零成本:

// webpack.config.js
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
  },
  // ...其他配置
};

加完这 5 行,第二次构建直接快 60%。注意:如果你用的是 CI/CD,记得在构建前清空缓存目录(一般是 node_modules/.cache/webpack),否则可能因为缓存不一致导致线上出问题。

然后是拆包(splitChunks),这个我踩过好几次坑。很多人一上来就照搬官方示例,结果把公共库拆得稀碎,反而增加了 HTTP 请求数。我的建议是:只拆 node_modules,且按大小阈值控制。

// webpack.config.js
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[\/]node_modules[\/]/,
        name: 'vendors',
        chunks: 'all',
        minChunks: 1,
        minSize: 100000, // 100KB 以上才拆
        maxSize: 500000, // 单个 chunk 不超过 500KB
      },
    },
  },
},

这里注意下,minSize 别设太小,否则会拆出一堆 10KB 的小文件,HTTP/2 虽然能并行,但 DNS 和 TCP 连接开销还是有的。我试过 50KB 以下的拆包,实际加载反而更慢。

这个场景最好用

开发环境和生产环境的优化策略完全不同。开发时我只关心热更新速度,生产环境才关心 bundle 体积。

开发环境我直接关掉 source map(除了调试时临时打开):

// webpack.dev.js
module.exports = {
  devtool: 'eval', // 最快的 source map 类型
  // 如果完全不需要调试,设为 false
};

实测 evalcheap-module-source-map 快 3 倍以上。当然,如果你在调样式或者 React 组件,可能需要保留,但日常逻辑开发真没必要。

生产环境则重点压缩和 Tree Shaking。确保你的 Babel 配置没把 ES6 模块转成 CommonJS,否则 Webpack 的 Tree Shaking 会失效:

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {
      modules: false, // 关键!保留 ES6 import/export
    }]
  ]
};

另外,别忘了用 CompressionPlugin 开启 Gzip:

const CompressionPlugin = require('compression-webpack-plugin');

// webpack.prod.js
plugins: [
  new Compressionplugin({
    algorithm: 'gzip',
    test: /.(js|css|html|svg)$/,
    threshold: 8192, // 8KB 以上才压缩
    minRatio: 0.8,
  }),
],

配合 Nginx 的 gzip_static on;,能省下 70% 的传输体积。不过注意:如果你们后端是 Node.js 直接 serve 静态文件,得自己处理 .gz 文件的响应头,否则浏览器会下载乱码。

踩坑提醒:这三点一定注意

第一,别盲目用 thread-loader。我之前在 CI 环境开了多线程,结果因为 CPU 核心数少,反而比单线程慢 20%。后来查了文档才发现,thread-loader 适合 CPU 密集型任务(比如 TS 编译),但对普通 JS 转译收益很小,还增加进程通信开销。现在我只在 TypeScript 项目里用它,而且限制线程数:

// 仅在 ts-loader 前加
{
  test: /.tsx?$/,
  use: [
    {
      loader: 'thread-loader',
      options: {
        workers: 2, // 别超过 CPU 核心数
      },
    },
    'ts-loader',
  ],
}

第二,resolve.alias 要谨慎。我曾经为了“路径好看”把所有组件都 alias 成 @/components,结果 Webpack 的依赖分析变慢,因为要遍历更多路径。现在我只 alias 项目根目录和核心工具库,其他一律用相对路径。

第三,别信“自动分析工具”的推荐。像 webpack-bundle-analyzer 虽然能看体积,但它不会告诉你哪些模块其实可以懒加载。我有个页面引入了整个 Ant Design,分析图里显示 1MB,但实际只需要 3 个组件。后来改成按需引入 + 动态 import,体积直接砍到 200KB。所以,工具只是参考,关键还是得懂业务逻辑。

高级技巧:动态 publicPath

这个很多人不知道,但特别实用。如果你的静态资源要部署到 CDN,但开发时又想用本地服务,可以用动态 publicPath

// webpack.config.js
output: {
  publicPath: (process.env.NODE_ENV === 'production')
    ? 'https://cdn.example.com/assets/'
    : '/',
},

但注意:Webpack 5 之后,你还可以在运行时动态设置:

// 在入口文件最顶部
__webpack_public_path__ = window.CDN_URL || '/';

这样连构建时都不用区分环境,适合多环境部署的场景。不过要确保这段代码在所有 import 之前执行,否则可能加载不到 chunk。

结尾碎碎念

Webpack 优化没有银弹,我这套配置在 10 人团队的中大型项目里跑了半年,基本稳定。但如果你的项目只有几个页面,可能根本不需要这么复杂——有时候删掉不用的依赖比优化配置更有效。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如 Module Federation 微前端下的优化),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流,毕竟我也不想再等两分钟打包了。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论