react项目优化之webpack

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