Webpack打包慢问题的完美解决方法

作者:知秋

时间:2026-10-21

浏览:369

本文详细解析Webpack打包速度慢的原因,特别是React生态库依赖导致的重复打包问题。通过配置externals手动分离第三方库,以及使用webpack.DllPlugin和DllReferencePlugin实现DLL机制,彻底解决开发环境打包延迟,显著提升构建效率。

文章目录

前言

Webpack打包慢的问题分析

在开发腾讯实习生 Mini 项目时,我使用了 React 全家桶,并引入 Webpack 作为打包工具。然而,开发过程中遇到了一个棘手的问题:由于 React 结合 React-Router、superagent、eventproxy 等第三方库,项目包含数百个模块,导致 Webpack 打包速度极慢。这不仅影响了开发体验,也严重降低了工作效率。

问题分析

首先,我们查看在未进行任何优化且使用 jsx 和 babel loader 的情况下,Webpack 的打包速度表现。

以下是我们的测试文件:


//test.js
var react = require('react');
var ReactAddonsCssTransitionGroup = require('react-addons-css-transition-group');
var reactDOM = require('react-dom');
var reactRouter = require('react-router');
var superagent = require("superagent");
var eventproxy = require("eventproxy");

运行


webpack test.js

在 2015 款 13 英寸 MacBook Pro(i5 处理器,全 SSD)上,性能表现如下:

Webpack打包慢的问题分析

确实如此,这些第三方库合计包含 668 个模块,全部打包需要超过 20 秒。

这意味着什么?每次修改业务代码后,gulp 或 Webpack 监测到变化都会重新打包,你需要等待整整 20 秒才能看到修改结果。

然而,真正需要重新打包的只有业务代码,这些第三方库无需重新打包,它们的存在只会拖累打包性能。因此,我们需要寻找方法来优化这一过程。

使用externals优化打包

配置externals

Webpack 允许配置 externals 将依赖库指向全局变量,从而不再打包该库。例如,对于这样一个文件:


import React from 'react';
console.log(React);

如果你在 Webpack.config.js 中配置了 externals:


module.exports = {
 externals: {
  'react': 'window.React'
 }
 //其它配置忽略...... 
};

这相当于告诉 Webpack 不要打包 react 模块,而是直接指向 window.React。但别忘了加载 react.min.js,确保全局环境中存在 React 变量。

我们来看看性能,由于不再打包 React,速度自然大幅提升,包体积也变小了:

Webpack打包慢的问题分析

配置externals的缺陷

如果问题如此简单解决,我就无需撰写本文。下面我们添加一个 react 的动画库 react-addons-css-transition-group 来测试:


import React from 'react';
import ReactAddonsCssTransitionGroup from 'react-addons-css-transition-group';
console.log(React);

没错,我也没有截错图。在添加了一个很小的动画库后,性能再次恶化。从模块数量来看,Webpack 显然重新打包了 react。

我们来分析一下,为什么一个很小的动画库会导致 Webpack 重新打包整个 react。找到 react-addons-css-transition-group 模块,查看其实现:


// react-addons-css-transition-group模块
// 入口文件 index.js
module.exports = require('react/lib/ReactCSSTransitionGroup');

这个动画模块仅有一行代码,作用是指向 react 下的一个子模块。我们再来看看这个子模块的实现:


// react模块
// react/lib/ReactCSSTransitionGroup.js
var React = require('./React');
var ReactTransitionGroup = require('./ReactTransitionGroup');
var ReactCSSTransitionGroupChild = require('./ReactCSSTransitionGroupChild');
//....剩余代码忽略

该子模块又反向依赖了整个 react 库的入口,这就是拖累 Webpack 性能的罪魁祸首。

总而言之,问题产生过程如下:

  1. Webpack 发现我们依赖了 react-addons-css-transition-group
  2. Webpack 在打包 react-addons-css-transition-group 时发现它依赖了 react 模块下的 ReactTransitionGroup.js 文件,于是去打包该文件。
  3. ReactTransitionGroup.js 依赖了整个 react 的入口文件 React.js。虽然我们设置了 externals,但 Webpack 不知道该入口文件等同于 react 模块本身,因此 Webpack 重新打包了整个 react。

读到这里你可能会有疑问,为什么不能把这个动画库也设置到 externals 里,这样不是就不用打包了吗?

问题在于,这个动画库并未提供生产环境的文件,或者说该库根本没有提供 react-addons-css-transition-group.min.js 文件。

这个问题不仅存在于 react-addons-css-transition-group 中,对于 react 的大多数现有库来说,都存在依赖关系复杂的问题。

初级解决方法:手动分离库

初级解决方法

解决此问题的方法是,手工打包这些模块,然后设置 externals,让 Webpack 不再打包它们。

我们需要这样一个 lib-bundle.js 文件:


window.__LIB["react"] = require("react");
window.__LIB["react-addons-css-transition-group"] = require("react-addons-css-transition-group");
// ...其它依赖包

在这里,我们将一些第三方库注册到 window.__LIB 下,这些库可作为底层基础库,免于重复打包。

然后执行 webpack lib-bundle.js lib.js,得到打包好的 lib.js。接着配置 externals:


var webpack = require('webpack');
module.exports = {
 externals: {
  'react': 'window.__LIB["react"]',
  'react-addons-css-transition-group': 'window.__LIB["react-addons-css-transition-group"]',
  // 其它库
 }
 //其它配置忽略...... 
};

此时,由于 externals 的存在,Webpack 打包时会避开这些模块众多、依赖关系复杂的库,将第三方模块的入口指向预先打包好的 lib.js 的入口 window.__LIB,从而只打包业务代码。

终极解决方法:使用DllPlugin

终极解决方法

上述方法本质上是一种动态链接库(dll)的思想,这在 Windows 系统中很常见。一个 dll 包是一个纯净的依赖库,本身不能运行,用于供 app 或业务代码引用。

同样,Webpack 最近也加入了此功能:webpack.DllPlugin。使用此功能需要将打包过程分为两步:

  1. 打包dll包
  2. 引用dll包,打包业务代码

首先我们来打包 dll 包,配置一个这样的 dll.config.js:


const webpack = require('webpack');

const vendors = [
 'react',
 'react-dom',
 'react-router',
 // ...其它库
];

module.exports = {
 output: {
  path: 'build',
  filename: '[name].js',
  library: '[name]',
 },
 entry: {
  "lib": vendors,
 },
 plugins: [
  new webpack.DllPlugin({
   path: 'manifest.json',
   name: '[name]',
   context: __dirname,
  }),
 ],
};

webpack.DllPlugin 的选项中:

  1. path 是 manifest.json 文件的输出路径,该文件将用于后续的业务代码打包;
  2. name 是 dll 暴露的对象名,需与 output.library 保持一致;
  3. context 是解析包路径的上下文,需与接下来配置的 webpack.config.js 一致。

运行 Webpack,会输出两个文件:一个是打包好的 lib.js,另一个是 manifest.json,其内容大致如下:


{
 "name": "vendor_ac51ba426d4f259b8b18",
 "content": {
  "./node_modules/react/react.js": 1,
  "./node_modules/react/lib/React.js": 2,
  "./node_modules/react/node_modules/object-assign/index.js": 3,
  "./node_modules/react/lib/ReactChildren.js": 4,
  "./node_modules/react/lib/PooledClass.js": 5,
  "./node_modules/react/lib/reactProdInvariant.js": 6,
  // ............
 }
}

接下来我们就可以快乐地打包业务代码啦,首先写好打包配置文件 webpack.config.js:


const webpack = require('webpack');
module.exports = {
 output: {
  path: 'build',
  filename: '[name].js',
 },
 entry: {
  app: './src/index.js',
 },
 plugins: [
  new webpack.DllReferencePlugin({
   context: __dirname,
   manifest: require('./manifest.json'),
  }),
 ],
};

webpack.DllReferencePlugin 的选项中:

  1. context 需要与之前保持一致,用于指导 Webpack 匹配 manifest 中库的路径;
  2. manifest 用来引入刚才输出的 manifest.json 文件。

DllPlugin 的本质做法与我们手动分离这些第三方库相同,但对于模块极多的应用来说,自动化明显提高了生产效率。

总结

以上就是关于彻底解决 Webpack 打包慢问题的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对本站的支持。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章