彻底解决Webpack打包慢的问题
这几天写腾讯实习生 Mini 项目的时候用上了 React 全家桶,当然同时引入了 Webpack 作为打包工具。但是开发过程中遇到一个很棘手的问题就是,React 加上 React-Router、superagent、eventproxy 这些第三方轮子一共有好几百个 module,Webpack 的打包速度极慢。这对于开发是非常不好的体验,同时效率也极低。 问题分析我们先来看一下完全没有任何优化的时候,Webpack 的打包速度(使用了jsx和babel的loader)。下面是我们的测试文件: //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款RMBP13,i5处理器,全SSD下,性能是这样的:
没错你没有看错,这几个第三方轮子加起来有整整668个模块,全部打包需要20多秒。 这意味着什么呢?你每次对业务代码的修改,gulp 或者 Webpack 监测到后都会重新打包,你要足足等20秒才能看到自己的修改结果。 但是需要重新打包的只有你的业务代码,这些第三方库是完全不用重新打包的,它们的存在只会拖累打包性能。所以我们要找一些方法来优化这个过程。 配置externalsWebpack 可以配置 externals 来将依赖的库指向全局变量,从而不再打包这个库,比如对于这样一个文件: import React from 'react'; console.log(React); 如果你在 Webpack.config.js 中配置了externals: module.exports = { externals: { 'react': 'window.React' } //其它配置忽略...... }; 等于让 Webpack 知道,对于 我们来看看性能,因为不用打包 React 了所以速度自然超级快,包也很小:
配置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 的罪魁祸首。 总而言之,问题是这样产生的:
读到这里你可能会有疑问,为什么不能把这个动画库也设置到 externals 里,这样不是就不用打包了吗? 问题就在于,这个动画库并没有提供生产环境的文件,或者说这个库根本没有提供 react-addons-css-transition-group.min.js 这个文件。 这个问题不只存在于 react-addons-css-transition-group 中,对于 react 的大多数现有库来说都有这个依赖关系复杂的问题。 初级解决方法所以对于这个问题的解决方法就是,手工打包这些 module,然后设置 externals ,让 Webpack 不再打包它们。 我们需要这样一个 window.__LIB["react"] = require("react"); window.__LIB["react-addons-css-transition-group"] = require("react-addons-css-transition-group"); // ...其它依赖包 我们在这里把一些第三方库注册到了 然后执行 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 打包的时候就会避开这些模块超多,依赖关系复杂的库,把这些第三方 module 的入口指向预先打包好的 终极解决方法上面我们提到的方法本质上就是一种动态链接库(dll)”的思想,这在 windows 系统下面是一种很常见的思想。一个dll包,就是一个很纯净的依赖库,它本身不能运行,是用来给你的 app 或者业务代码引用的。 同样的 Webpack 最近也新加入了这个功能:
首先我们来打包ddl包,首先配置一个这样的 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 的选项中:
运行Webpack,会输出两个文件一个是打包好的 { "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,// ............ } } 接下来我们就可以快乐地打包业务代码啦,首先写好打包配置文件 const webpack = require('webpack'); module.exports = { output: { path: 'build',entry: { app: './src/index.js',plugins: [ new webpack.DllReferencePlugin({ context: __dirname,manifest: require('./manifest.json'),}; webpack.DllReferencePlugin 的选项中:
DllPlugin 本质上的做法和我们手动分离这些第三方库是一样的,但是对于包极多的应用来说,自动化明显加快了生产效率。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |