同步自我的博客
开门见山,由于我们项目的前端代码只有一个bundle
,所有代码都在一个js文件里,随着功能不断的堆叠,体积已经到无法忍受的地步了(gzip后即将突破300k),导致首屏的时间不停的涨啊涨,最近一周富裕了一点人力赶紧做一次优化,暂时缓住了势头。
react+webapck的优化文章现在一搜一大把,这次只说说我的技术方案,以及我是如何分析和优化的。
技术方案
首先,我先说下,gzip
,cdn
等等前端优化手段我们都是有的,这次主要是为了解决bundle
过大的问题。
我们项目的总体架构很简单易懂,react
全家桶:react
+redux
,哈哈。共有三个系统,每个系统有自己单独的工程。由于是Hybrid
应用,为了尽可能贴合APP
内的切页效果,每个工程又都是采用的是单页+多页的形式。
由于系统多+单页多页混合,导致我们的路由比较混乱,本来打算上react-router
进行一次大的改版,但是时间不够充裕,综合时间,开发成本角度来看,我定了一个简单并可以快速实施的方案:
抽取出三个工程的公共代码打包出一个
bundle
。比如react
,redux
,redux-thunk
以及一些polyfill
,因为这些都是长时间不会变动的代码,所以可以最大程度的命中缓存,并且每次发布不需要重新下载这部分代码,在项目中就用externals
的方案去引用这些代码。code split
,每个工程按照多页的路由做代码切割,每个多页路由都有自己的bundle
。异步加载,每个多页路由都会对应各自的辅助页面和组件代码,都使用异步加载的方式。
如何优化bundle体积
code split+异步加载
这个很简单,使用webpack
的requre.ensure
就可以了
addPage(cb){
require.ensure([], function () {
cb({
Page1: require('../../pages/Page1'),
Page2: require('../../pages/Page2')
})
});
}
这里Page1
和Page2
的代码都是异步加载的,具体的部分涉及到路由的设计,我们目前的方案非常的简单和随便,就不细说了。我也看了react-router
的解决方案,觉得写起来复杂了一些,后续可能会在寻找更好的方案或者自己撸一个。
这里需要注意,虽然我们的js
代码做了拆分,css
文件还是希望打包成一个,所以需要给ExtractTextPlugin
增加allChunks
的配置。
new options.ExtractTextPlugin('[name]@[contenthash].css', {allChunks: true})
externals
我将react
,redux
等等公用的代码打包成一个lib.js
,然后暴露在全局变量上。每个工程里都会先去引用这个lib.js
,在webpack
的配置里就只需要配置上externals
就可以了
其他插件
我们还用了一些其他插件来尽可能的优化体积
UglifyJsPlugin
不多说了,代码混淆压缩DedupePlugin
消除重复引用的模块,好像webpack2
已内置,使用webpack2
的可以忽略OccurrenceOrderPlugin
让依赖次数多的模块靠前分到更小的id来达到输出更多的代码CommonsChunkPlugin
这个我们没有用,但是大家可以去看看,对于多页应用它可以帮助你将一些公共代码打包
如何分析bundle过大的问题
在做代码拆分的时候我曾遇到过一个问题,如何确认我的代码已经是最优的了,如何确认我无法继续优化了?这就需要我们去查看,这个bundle
究竟打包了哪些代码,是否这些都是我们需要的。
首先我推荐一个网址,这里介绍了很多webpack
优化的工具。
我自己推荐两个bundle
体积的可视化分析工具
具体如何使用我就不介绍了,它们的文档写的很清楚大家可以去文档上看,他们都可以很清楚的看到每个bundle
分别打包了哪些代码,哪些占据了最大的体积,也可以观察哪些代码其实是无用的可以优化掉的。
这里我还遇到过一个问题,比如我在detail.chunk.js
里发现引入了一个loading
组件,但是我映象里详情页并没有引入loading
组件呀,这时候就需要去寻找loading
是被谁依赖了。之前我都是用webstorm的find usages
一点点的去看引用关系,其实可以用webpack
提供的一个官方工具来做这件事。
首先,你需要这么启动webpack
webpack --profile --json > stats.json
此时会生成一个stats.json
文件,之后在官方分析工具里上传文件即可对你的bundle进行分析。
这里我用官方的例子简单说下
上传你的
json
文件,长传后会看到这么一个界面,会简单描述你的webpack
的版本,有多少modules
,多少chunks
等等点击
chunks
,可以看到所有chunks
的描述,左边是chunks
的id,然后有namse
,有多少modules
,大小,引用它的chunks
是谁、即parents
,假如我们需要分析id
为1的chunk
,只需要点击左边的id
这里你可以看到更详细的信息,这里最重要的是两个,
reasons
是引用这个chunks
的模块,modules
是这个chunks
所引用的modules
这里你发现有一个模块不是你想要的
modules
,你只需要点击这个模块的id,再去查看reasons
就可以看到这个模块是被谁引入的
如何优化本地开发体验和打包速度
webpack吐槽的常态 —— 打包慢,这里说一下我们这边做过的优化。
缩小文件搜索范围
将resolve.modules
配置为node_modules
,像使用 impot _ from "lodash"
这种时webpack遍历向上递归查到node_modules
,但通常只有一个node_modules
,为了减少可以直接写明node_modules
的地址
loader
也可以设置需要生效的目录地址
比如babel
的loader
可以只对src目录里的代码进行编译,忽略庞大的node_modules
{
test:/\.js$/,
loader:'babel-oader',
include:path.resolve(__dirname,'src')
}
使用alias
发布到npm的库大多包含两个目录,一个是放cmd模块化的lib目录,一个是所有文件合并成的dist目录,多数入口文件是指向lib的。默认情况下webpack会去读lib目录下的入口文件再去递归加载其他以来的文件,这个过程非常耗时,alias
可以让webpack直接使用dist目录的整体文件减少递归
使用noParse
有些库是自成一体,不需要依赖别的库的,webpack无需解析他们的依赖,可以配置这些文件脱离Webpack解析。
happyPack
happyPack的文档也写的很好,就不复制粘贴了,大家可以自行去阅读文档,简单地说,它主要是利用多进程+缓存使得build
更快,这大幅减少了我们在编译机上编译的时间。
后评估
先说说优化完后的结果,由于react
的体积过大,lib
就有60k+,基本已经不能继续优化了,加上我们的路由设计的很不好,首屏的bundle依然有70k+,总的来说,首屏从280k降低到140k左右。
但是,根据监控的效果来看,页面js下载的总体时间和白屏时间都只降低了30%左右,这并不符合我的心理预期,想了想除了http请求变多以外并没有别的副作用,后续会继续深入的分析一下为什么优化的效果没达到预期。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。