Vue项目优化实践 —— CDN + Gzip + Prerender

栏目: JavaScript · 发布时间: 5年前

内容简介:和很多小伙伴一样,我在开发通过可以看到未经优化的

和很多小伙伴一样,我在开发 Vue 项目时也是基于官方 vue-cli@2webpack 模版,但随着项目越做越大,依赖的第三方 npm 包越来越多,构建之后的文件也会越来越大,尤其是 vendor.js ,甚至会达到 2M 左右。再加上又是单页应用,这就会导致在网速较慢或者服务器带宽有限的情况出现长时间的白屏。为了解决这个问题,我做了一些探索,在几乎不需要改动业务代码的情况下,找到了三种有明显效果的优化方案 —— CDN + Gzip + Prerender 。我把这些方法整理了一下,放在了 Github仓库 上,意图通过不同的分支来展示不同的优化方式,对 Vue 项目性能的影响。你可以直接克隆下来试一试,也得益于有 git 历史,你也可以很方便的查看具体的改动细节。下面我将通过一个简单的项目来展示这三种优化方案的效果。

一、首先准备一个 简单的项目

通过 vue-cli@2webpack 模版生成,只包含最基础的 Vue 三件套 ———— vuevue-routervuex 以及常用的 element-uiaxios 。拆分两个路由——“首页”和“通讯录”,通过 axios 异步获取一个通讯录名单,并利用 element-ui 的表格展示。直接 build ,不做任何优化处理,以作参照。

1.1 构建后文件说明:

  1. app.css : 压缩合并后的样式文件。
  2. app.js :主要包含项目中的 App.vuemain.jsrouterstore 等业务代码。
  3. vendor.js :主要包含项目依赖的诸如 vuexaxios 等第三方库的源码,这也是为什么这个文件如此之大的原因,下一步将探索如何优化这一块,毕竟随着项目的开发,依赖的库也能会越来越多。
  4. 数字.js :以0、1、2、3等数字开头的 js 文件,这些文件是各个路由切分出的代码块,因为我拆分了两个路由,并做了路由懒加载,所以出现了0和1两个 js 文件。
  5. mainfest.jsmainfest 的英文有 清单、名单的意思 ,该文件包含了加载和处理路由模块的逻辑
Vue项目优化实践 —— CDN + Gzip + Prerender

1.2 禁用浏览器缓存,网速限定为 Fast 3G 下的 Network 图(运行在本地的 nginx 服务器上

可以看到未经优化的 base 版本在 Fast 3G 的网络下大概需要7秒多的时间才加载完毕

Vue项目优化实践 —— CDN + Gzip + Prerender

二、 CDN 优化

  1. 将依赖的 vuevue-routervuexelement-uiaxios 这五个库,全部改为通过 CDN 链接获取。借助 HtmlWebpackPlugin ,可以方便的使用循环语法在 index.html 里插入 jscssCDN 链接。这里的 CDN 大部分使用的jsDelivr 提供的。
<!-- CDN文件,配置在config/index.js下 -->
<% for (var i in htmlWebpackPlugin.options.css) { %>
<link href="<%= htmlWebpackPlugin.options.css[i] %>" rel="stylesheet">
<% } %>
<% for (var i in htmlWebpackPlugin.options.js) { %>
<script src="<%= htmlWebpackPlugin.options.js[i] %>"></script>
<% } %>
复制代码
  1. build/webpack.base.conf.js 中添加如下代码,这使得在使用 CDN 引入外部文件的情况下,依然可以在项目中使用 import 的语法来引入这些第三方库,也就意味着你不需要改动项目的代码,这里的键名是 importnpm 包名,键值是该库暴露的全局变量。webpack文档参考链接。
externals: {
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'Vuex',
    'element-ui':'ELEMENT',
    'axios':'axios'
  }
复制代码
  1. 卸载依赖的 npm 包, npm uninstall axios element-ui vue vue-router vuex
  2. 删除 main.jselement-ui 相关代码。

具体细节可以查看 git 的历史记录

2.1 比对添加 CDN 前后构建的文件:

优化后:

Vue项目优化实践 —— CDN + Gzip + Prerender

优化前:

Vue项目优化实践 —— CDN + Gzip + Prerender

可以看出:

  1. app.css : 因为不再通过 import 'element-ui/lib/theme-chalk/index.css' ,而是直接通过 CDN 链接的方式引入 element-ui 样式,使得文件小到了 bytes 级别,因为它现在仅包含少量的项目的 css
  2. app.js :几乎无变化,因为这里面主要还是自己业务的代码。
  3. vendor.js :将5个依赖的 js 全部转为 CDN 链接后,已经小到了不足 1KB ,其实里面已经没有任何第三方库了。
  4. 数字.jsmainfest.js :这些文件本来就很小,变化几乎可以忽略。

2.2 同样,禁用浏览器缓存,网速限定为 Fast 3G 下的 Network 图(运行在本地的 nginx 服务器上

可以看出相同的网络环境下,加载从原来的7秒多,提速到现在的3秒多,提升非常明显。而且更重要的一点是原本的方式,所有 的 jscss 等静态资源都是请求的我们自己的 nginx 服务器,而现在大部分的静态资源都请求的是第三方的 CDN 资源, 这不仅可以带来速度上的提升,在高并发的时候,这无疑大大降低的自己服务器的带宽压力,想象一下原来首屏900多KB的文件 现在仅剩20KB是请求自己服务器的!

Vue项目优化实践 —— CDN + Gzip + Prerender

三、 Gzip 优化

使用 Gzip 两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。

3.1 如何开启 gzip 压缩

开启 gzip 的方式主要是通过修改服务器配置,以 nginx 服务器为例,下图是,使用同一套代码,在仅改变服务器的 gzip 开关状态的情况下的 Network 对比图

未开启 gzip 压缩:

Vue项目优化实践 —— CDN + Gzip + Prerender

开启 gzip 压缩:

Vue项目优化实践 —— CDN + Gzip + Prerender

开启 gzip 压缩后的响应头

Vue项目优化实践 —— CDN + Gzip + Prerender

从上图可以明显看出开启 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 服务器下,看看有什么区别:

Vue项目优化实践 —— CDN + Gzip + Prerender
Vue项目优化实践 —— CDN + Gzip + Prerender

我们会发现构建之后的文件多了一些 js.gzcss.gz 的文件,而且 vendor.js 变得更小了,这其实是因为我们开启了 nginxgzip_static on; 选项, 如果 gzip_static 设置为 on ,那么就会使用同名的 .gz 文件,不会占用服务器的CPU资源去压缩。

3.3 前端快速搭建基于 nodegzip 服务

无法搭建 nginx 环境的前端小伙伴也可以按如下步骤快速启动一个带 gzipexpress 服务器

npm i express compression
serve.js
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')
  })
复制代码
  1. 执行 node server.js

下图是 express 开启 gzip 的响应头:

Vue项目优化实践 —— CDN + Gzip + Prerender

四、 Prerender 预渲染

大家都是知道:常见的 Vue 单页应用构建之后的 index.html 只是一个包含根节点的空白页面,当所有需要的 js 加载完毕之后,才会开始解析并创建 vnode ,然后再渲染出真实的 DOM 。当这些 js 文件过大而网速又很慢或者出现意料之外的报错时,就会出现所谓的白屏,相信做 Vue 开发的小伙伴们一定都遇到过这种情况。而且单页应用还有一个很大的弊端就是对 SEO 很不友好。那么如何解决这些问题呢?—— SSR 当然是很好的解决的方案,但这也意为着一定的学习成本和运维成本,而如果你已经有了一个现成的 vue 单页应用,转向 SSR 也并不是一个无缝的过程。那么 预渲染 就显得更加合适了。只需要安装一个 webpack 的插件 + 一些简单的 webpack 配置就可以解决上述的两个问题。

4.1 如何将单页应用转为预渲染

  1. 你需要将 router 设为 history 模式,并相应的调整服务器配置,这并不复杂。
  2. npm i prerender-spa-plugin --save-dev
  3. build/webpack.prod.conf.js 下添加如下配置(没有路由懒加载的情况)。
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
    }
  })
复制代码
  1. config/index.jsbuild 中的 assetsPublicPath 字段设置为 '/' ,这是因为当你使用预渲染时,路由组件会编译成相应文件夹下的 index.html ,它会依赖 static 目录下的文件,而如果使用相对路径则会导致依赖的路径错误,这也要求预渲染的项目最好是放在网站的根目录下(这个坑我已经在 prerender-spa-plugin 仓库提过 ISSUE 了,不过借助 postProcess ,自己再写一个正则表达式,也能实现,如果你有这方面的需求,可以参考下面 路由懒加载带来的坑 )。
  2. 调整 main.js
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 早已渲染好了静态页面。

Vue项目优化实践 —— CDN + Gzip + Prerender

4.2 效果如何?

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

Vue项目优化实践 —— CDN + Gzip + Prerender

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 标签注入到生成的 htmlhead 标签内。这会导致它先于 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
  }
复制代码

除了这种解决方案,还有两种不推荐的解决方案:

  1. 索性不使用路由懒加载。
  2. HtmlWebpackPlugininject 字段设置为 'head' ,这样 app.js,vendor.js,manifest.js 就会插入到 head 里,并在异步的 script 标签上面。 但由于普通的 script 是同步的,在他们全部加载完毕之前,页面是无法渲染的,也就违背了 prerender 的初衷,而且你还需要对 main.js 作如下修改,以确保 Vue 在实例化的时候可以找到 <div id="app"></div> ,并正确挂载。
const app = new Vue({
      // ...
    })
    document.addEventListener('DOMContentLoaded', function () {
      app.$mount('#app')
    })
复制代码

总结

虽然官方的脚手架已经提供很多开箱即用的优化,比如 css 压缩合并, js 压缩与模块化,小图片转 base64 等等,但我们能做的还很多。我没有提及代码级别的优化细节,也是希望给大家提供一些可实践的方案。上述三种方案或多或少都会给你项目带来一些收益。优化也是一门玄学,可研究的东西很多。也希望其他小伙伴可以在评论区提供宝贵意见,或者直接向我的这个项目 vue-optimizationbase 分支提交 PR ,好的方案我会采纳并整理。目前三种方案整合的最终结果我已经放在 master 分支下,你可以克隆下来并在此基础上开发你的项目。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

构建高可用Linux服务器(第3版)

构建高可用Linux服务器(第3版)

余洪春 / 机械工业出版社 / 2014-10 / 79.00元

《构建高可用Linux服务器(第3版)》是Linux运维领域公认的经典畅销书,是国内51CTO、IT168等知名网站和多位资深运维专家共同推荐的运维工程师必备的工具书! “酒哥”在Linux运维领域潜心实践近10年,一直在运维一线,技术和思维都紧跟时代的发展,非常清楚运维工程师们需要什么,应该学习什么。本书不仅是他近10年工作经验的结晶,同时也是他的数万名读者和数十万粉丝共同需求和集体智慧的......一起来看看 《构建高可用Linux服务器(第3版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具