黑马程序员技术交流社区

标题: 【上海校区】Vue CLI 2&3 下的项目优化实践 —— CDN + Gzip + Pr... [打印本页]

作者: 不二晨    时间: 2018-10-11 08:52
标题: 【上海校区】Vue CLI 2&3 下的项目优化实践 —— CDN + Gzip + Pr...
前言
这些优化方案适用于 Vue CLI 2 和 Vue CLI 3 ,文章主要基于Vue CLI 2进行介绍,关于如何在Vue CLI 3中进行相关的webpack调整,我已经放在了 vue-cli3-optimization 这个仓库下,并配有详细的注释,且额外添加方便Sass使用的loader,使用Sass时无需再在每个需要引入变量和mixin的地方,每次都很麻烦的@import。下面将详细介绍这些优化方案的实践方式和效果:
和很多小伙伴一样,我在开发Vue项目时也是基于官方vue-cli@2的webpack模版,但随着项目越做越大,依赖的第三方npm包越来越多,构建之后的文件也会越来越大,尤其是vendor.js,甚至会达到2M左右。再加上又是单页应用,这就会导致在网速较慢或者服务器带宽有限的情况出现长时间的白屏。为了解决这个问题,我做了一些探索,在几乎不需要改动业务代码的情况下,找到了三种有明显效果的优化方案 —— CDN + Gzip + Prerender。我把这些方法整理了一下,放在了 Github仓库 上,意图通过不同的分支来展示不同的优化方式,对Vue项目性能的影响。你可以直接克隆下来试一试,也得益于有git历史,你也可以很方便的查看具体的改动细节。下面我将通过一个简单的项目来展示这三种优化方案的效果。
一、首先准备一个简单的项目通过vue-cli@2的webpack模版生成,只包含最基础的Vue三件套 ———— vue、vue-router、vuex以及常用的element-ui和axios。拆分两个路由——“首页”和“通讯录”,通过axios异步获取一个通讯录名单,并利用element-ui的表格展示。直接build,不做任何优化处理,以作参照。
1.1 构建后文件说明:

1.2 禁用浏览器缓存,网速限定为Fast 3G下的Network图(运行在本地的nginx服务器上可以看到未经优化的base版本在Fast 3G的网络下大概需要7秒多的时间才加载完毕


二、CDN 优化<!-- CDN文件,配置在config/index.js下 --><% for (var i in htmlWebpackPlugin.options.css) { %><link href="<%= htmlWebpackPlugin.options.css %>" rel="stylesheet"><% } %><% for (var i in htmlWebpackPlugin.options.js) { %><script src="<%= htmlWebpackPlugin.options.js %>"></script><% } %>复制代码  externals: {    'vue': 'Vue',    'vue-router': 'VueRouter',    'vuex': 'Vuex',    'element-ui':'ELEMENT',    'axios':'axios'  }复制代码具体细节可以查看git的历史记录
2.1 比对添加 CDN 前后构建的文件:优化后:
优化前:可以看出:
2.2 同样,禁用浏览器缓存,网速限定为Fast 3G下的Network图(运行在本地的nginx服务器上可以看出相同的网络环境下,加载从原来的7秒多,提速到现在的3秒多,提升非常明显。而且更重要的一点是原本的方式,所有的js和css等静态资源都是请求的我们自己的nginx服务器,而现在大部分的静态资源都请求的是第三方的CDN资源,这不仅可以带来速度上的提升,在高并发的时候,这无疑大大降低的自己服务器的带宽压力,想象一下原来首屏900多KB的文件现在仅剩20KB是请求自己服务器的!

三、Gzip 优化使用Gzip两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。
3.1 如何开启gzip压缩开启gzip的方式主要是通过修改服务器配置,以nginx服务器为例,下图是,使用同一套代码,在仅改变服务器的gzip开关状态的情况下的Network对比图
未开启gzip压缩:


开启gzip压缩:


开启gzip压缩后的响应头


从上图可以明显看出开启gzip前后,文件大小有三四倍的差距,加载速度也从原来的7秒多,提升到3秒多
附上nginx的配置方式
http {  gzip on;  gzip_static on;  gzip_min_length 1024;  gzip_buffers 4 16k;  gzip_comp_level 2;  gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;  gzip_vary off;  gzip_disable "MSIE [1-6]\.";}复制代码3.2 前端能为gzip做点什么我们都知道config/index.js里有一个productionGzip的选项,那么它是做什么用的?我们尝试执行npm install --save-dev compression-webpack-plugin@1.x,并把productionGzip设置为true,重新build,放在nginx服务器下,看看有什么区别:




我们会发现构建之后的文件多了一些js.gz和css.gz的文件,而且vendor.js变得更小了,这其实是因为我们开启了nginx的gzip_static on;选项,如果gzip_static设置为on,那么就会使用同名的.gz文件,不会占用服务器的CPU资源去压缩。
3.3 前端快速搭建基于node的gzip服务无法搭建nginx环境的前端小伙伴也可以按如下步骤快速启动一个带gzip的express服务器
  var express = require('express')  var app = express()  // 开启gzip压缩,如果你想关闭gzip,注释掉下面两行代码,重新执行`node server.js`  var compression = require('compression')  app.use(compression())  app.use(express.static('dist'))  app.listen(3000,function () {    console.log('server is runing on http://localhost:3000')  })复制代码下图是express开启gzip的响应头:


四、Prerender 预渲染大家都是知道:常见的Vue单页应用构建之后的index.html只是一个包含根节点的空白页面,当所有需要的js加载完毕之后,才会开始解析并创建vnode,然后再渲染出真实的DOM。当这些js文件过大而网速又很慢或者出现意料之外的报错时,就会出现所谓的白屏,相信做Vue开发的小伙伴们一定都遇到过这种情况。而且单页应用还有一个很大的弊端就是对SEO很不友好。那么如何解决这些问题呢?—— SSR当然是很好的解决的方案,但这也意为着一定的学习成本和运维成本,而如果你已经有了一个现成的vue单页应用,转向SSR也并不是一个无缝的过程。那么预渲染就显得更加合适了。只需要安装一个webpack的插件 + 一些简单的webpack配置就可以解决上述的两个问题。
4.1 如何将单页应用转为预渲染  const PrerenderSPAPlugin = require('prerender-spa-plugin')  ...  new PrerenderSPAPlugin({    staticDir: config.build.assetsRoot,    routes: [ '/', '/Contacts' ], // 需要预渲染的路由(视你的项目而定)    minify: {      collapseBooleanAttributes: true,      collapseWhitespace: true,      decodeEntities: true,      keepClosingSlash: true,      sortAttributes: true    }  })复制代码  new Vue({    router,    store,    render: h => h(App)  }).$mount('#app', true) // https://ssr.vuejs.org/zh/guide/hydration.html复制代码执行npm run build,你会发现,dist目录和以往不太一样,不仅多了与指定路由同名的文件夹而且index.html早已渲染好了静态页面。


4.2 效果如何?和之前一样,我们依然禁用缓存,将网速限定为Fast 3G(运行在本地的nginx服务器上)。可以看到,在vendor.js还没有加载完毕的时候(大概有700多kB,此时只加载了200多kB),页面已经完整的呈现出来了。事实上,只需要index.html和app.css加载完毕,页面的静态内容就可以很好的呈现了。预渲染对于这些有大量静态内容的页面,无疑是很好的选择。


4.3 路由懒加载带来的坑如果你的项目没有做路由懒加载,那么你大可放心的按上面所说的去实践了。但如果你的项目里用了,你应该会看到webpackJsonp is not defined的报错。这个因为prerender-spa-plugin渲染静态页面时,也会将类似于<script src="/static/js/0.9231fc498af773fb2628.js" type="text/javascript" async charset="utf-8"></script>这样的异步script标签注入到生成的html的head标签内。这会导致它先于app.js,vendor.js,manifest.js(位于body底部)执行。(async只是不会阻塞后面的DOM解析,这并不意味这它最后执行)。而且当这些js加载完毕后,又会在head标签重复创建这个异步的script标签。虽然这个报错不会对程序造成影响,但是最好的方式,还是不要把这些异步组件直接渲染到最终的html中。好在prerender-spa-plugin提供了postProcess选项,可以在真正生成html文件之前做一次处理,这里我使用一个简单的正则表达式,将这些异步的script标签剔除。本分支已经使用了路由懒加载,你可以直接查看git历史,比对文件和base分支的变化来对你的项目进行相应调整。
  postProcess (renderedRoute) {    renderedRoute.html = renderedRoute.html.replace(/<script.*src=".*[0-9]+\.[0-9a-z]*\.js"><\/script>/,'')    return renderedRoute  }复制代码除了这种解决方案,还有两种不推荐的解决方案:
    const app = new Vue({      // ...    })    document.addEventListener('DOMContentLoaded', function () {      app.$mount('#app')    })复制代码总结虽然官方的脚手架已经提供很多开箱即用的优化,比如css压缩合并,js压缩与模块化,小图片转base64等等,但我们能做的还很多。我没有提及代码级别的优化细节,也是希望给大家提供一些可实践的方案。上述三种方案或多或少都会给你项目带来一些收益。优化也是一门玄学,可研究的东西很多。也希望其他小伙伴可以在评论区提供宝贵意见,或者直接向我的这个项目 vue-optimization 的base分支提交PR,好的方案我会采纳并整理。目前三种方案整合的最终结果我已经放在 master 分支下,你可以克隆下来并在此基础上开发你的项目。


作者:郑昊川
链接:https://juejin.im/post/5b97b84ee51d450e6c7492f6




作者: 不二晨    时间: 2018-10-15 16:05
奈斯




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2