咱们先来看一下彻底没有任何优化的时候,Webpack 的打包速度(使用了jsx和babel的loader)。下面是咱们的测试文件:node
//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");
运行react
webpack test.js
在个人2015款RMBP13,i5处理器,全SSD下,性能是这样的:jquery
没错你没有看错,这几个第三方轮子加起来有整整668个模块,所有打包须要20多秒。webpack
这意味着什么呢?你每次对业务代码的修改,gulp 或者 Webpack 监测到后都会从新打包,你要足足等20秒才能看到本身的修改结果。nginx
可是须要从新打包的只有你的业务代码,这些第三方库是彻底不用从新打包的,它们的存在只会拖累打包性能。因此咱们要找一些方法来优化这个过程。web
Webpack 能够配置 externals 来将依赖的库指向全局变量,从而再也不打包这个库,好比对于这样一个文件:json
import React from 'react'; console.log(React);
若是你在 Webpack.config.js 中配置了externals:gulp
module.exports = { externals: { 'react': 'window.React' } //其它配置忽略...... };
等于让 Webpack 知道,对于 react
这个模块就不要打包啦,直接指向 window.React
就好。不过别忘了加载 react.min.js,让全局中有 React
这个变量。bootstrap
咱们来看看性能,由于不用打包 React 了因此速度天然超级快,包也很小:
问题若是就这么简单地解决了的话,那我就不必写这篇文章了,下面咱们加一个 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 的罪魁祸首。
总而言之,问题是这样产生的:
Webpack 发现咱们依赖了 react-addons-css-transition-group
Webpack 去打包 react-addons-css-transition-group 的时候发现它依赖了 react 模块下的一个叫 ReactTransitionGroup.js 的文件,因而 Webpack 去打包这个文件。
ReactTransitionGroup.js 依赖了整个 react 的入口文件 React.js,虽然咱们设置了 externals ,可是 Webpack 不知道这个入口文件等效于 react 模块自己,因而咱们可爱又敬业的 Webpack 就把整个 react 又从新打包了一遍。
读到这里你可能会有疑问,为何不能把这个动画库也设置到 externals 里,这样不是就不用打包了吗?
问题就在于,这个动画库并无提供生产环境的文件,或者说这个库根本没有提供 react-addons-css-transition-group.min.js 这个文件。
这个问题不仅存在于 react-addons-css-transition-group 中,对于 react 的大多数现有库来讲都有这个依赖关系复杂的问题。
因此对于这个问题的解决方法就是,手工打包这些 module,而后设置 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 打包的时候就会避开这些模块超多,依赖关系复杂的库,把这些第三方 module 的入口指向预先打包好的 lib.js
的入口 window.__LIB
,从而只打包咱们的业务代码。
上面咱们提到的方法本质上就是一种动态连接库(dll)”的思想,这在 windows 系统下面是一种很常见的思想。一个dll包,就是一个很纯净的依赖库,它自己不能运行,是用来给你的 app 或者业务代码引用的。
一样的 Webpack 最近也新加入了这个功能:webpack.DllPlugin
。使用这个功能须要把打包过程分红两步:
打包ddl包
引用ddl包,打包业务代码
首先咱们来打包ddl包,首先配置一个这样的 ddl.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 的选项中:
path
是 manifest.json
文件的输出路径,这个文件会用于后续的业务代码打包;
name
是dll暴露的对象名,要跟 output.library
保持一致;
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 的选项中:
context
须要跟以前保持一致,这个用来指导 Webpack 匹配 manifest
中库的路径;
manifest
用来引入刚才输出的 manifest.json
文件。
DllPlugin 本质上的作法和咱们手动分离这些第三方库是同样的,可是对于包极多的应用来讲,自动化明显加快了生产效率。
PS:
其实还有一个速度的优化点,就是配置babel,让它排除一些文件,当loader这些文件时不进行转换,自动跳过;可在.babelrc文件中配置,示例:
{
"presets": [ "es2015" ], "ignore":[ "jquery.js", "jquery.min.js", "angular.js", "angular.min.js", "bootstrap.js", "bootstrap.min.js" ] }