记一次 VUE 项目优化实践

栏目: CSS · 发布时间: 6年前

内容简介:爱康体检宝 PC(www.tijianbao.com/) 算是一个“老”项目,为什么说“老”呢,因为在前端技术日新月异,每天都有新知识、新概念,甚至新框架的今天,它还是基于vue-cli 2.x、webpack 3.x构建,显然有些老了。其次,在早期开始这个项目的时候,由于仓促上线,也没有过多的考虑性能及加载问题,目前网站上使用的图片未经过裁切,所有的库都打包到一个 vendor 里,首屏加载时间太长,等等这些问题使网站的用户体验不是太好,基于各方面的原因,决定对它进行一次优化,主要从以下几个方面:htt

爱康体检宝 PC(www.tijianbao.com/) 算是一个“老”项目,为什么说“老”呢,因为在前端技术日新月异,每天都有新知识、新概念,甚至新框架的今天,它还是基于vue-cli 2.x、webpack 3.x构建,显然有些老了。其次,在早期开始这个项目的时候,由于仓促上线,也没有过多的考虑性能及加载问题,目前网站上使用的图片未经过裁切,所有的库都打包到一个 vendor 里,首屏加载时间太长,等等这些问题使网站的用户体验不是太好,基于各方面的原因,决定对它进行一次优化,主要从以下几个方面:

使用 https 及 升级成 http/2 协议

https 主要带来安全性方面的提升,而且 http/2 依赖于 https,只有使用 https 协议的站点可以升级 http/2 协议。

http/2 带来了一系列的改动和优化,主要如下:

  • 每个服务器只用一个连接。HTTP/2 对每个服务器只使用一个连接,而不是每个文件一个连接。这样,就省掉了多次建立连接的时间,这个时间对 TLS 尤其明显,因为 TLS 连接费时间。
  • 加速 TLS 交付。HTTP/2 只需一次耗时的 TLS 握手,并且通过一个连接上的多路利用实现最佳性能。HTTP/2 还会压缩首部数据,省掉 HTTP/1.x 时代所需的一些优化工作,比如拼接文件,从而提高缓存利用率。
  • 简化 Web 应用。使用 HTTP/2 可以让 Web 开发者省很多事,因为不用再做那些针对 HTTP/1.x 的优化工作了。
  • 适合内容混杂的页面。HTTP/2 特别适合混合了 HTML、CSS、JavaScript、图片和有限多媒体的传统页面。浏览器可以优先安排那些重要的文件请求,让页面的关键部分先出现,快出现。
  • 更安全。通过减少 TLS 的性能损失,可以让更多应用使用 TLS,从而让用户信息更安全。

这里有一篇来自 google 的HTTP/2 简介 更为全面和权威。

配置主要是在编译 nginx 时加上 with-http_ssl_module 模块和 with-http_v2_module模块

./configure --with-http_v2_module --with-http_ssl_module
复制代码

配置服务器 conf 文件

server {
 listen 443 ssl http2 default_server;

 ssl_certificate server.crt;
 ssl_certificate_key server.key;
 ...
}
复制代码

然后重启服务器,完成升级

合理控制缓存

缓存对于 web 应用程序至关重要,合理控制缓存可以有效提升 web 性能,我们之前有些域下未做明确的缓存管理,虽然浏览器有默认的缓存机制,但是由于默认的机制未必能满足我们的要求,而且各浏览器的默认机制不同,可能造成 web 程序的表现也不同,所以很有必要对各资源的缓存进行精细控制。

关于浏览器缓存,我写过一篇文章 做了详细介绍,这里只说具体的实施细节:

  • 开启 gzip (服务器优化的一总分,不属于缓存范畴)
  • 开启 etag
  • 对 html 类型的文件设置过期时间为 80s
  • 对 api 请求设置无缓存
  • 对静态资源设置一个较长时间的缓存

具体 nginx 配置如下:

# 配置 gzip
        gzip on;
        gzip_min_length 0k;
        gzip_comp_level 1;
        gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        gzip_vary on;
        gzip_disable "MSIE [1-6]\.";

# 开启 etag
        etag off;

# 不缓存接口
        location ~* \.(?:manifest|appcache|xml|json)$ {
                add_header Cache-Control "no-cache";
        }
        
# 设置 html 过期时间为 80s
        location ~* \.(?:html)$ {
                add_header Cache-Control "max-age=80";
        }

# 给静态资源设置一个长期缓存
        location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
                expires 1M;
                access_log off;
                add_header Cache-Control "public";
        }
        # CSS and Javascript
        location ~* \.(?:css|js)$ {
                expires 1y;
                access_log off;
                add_header Cache-Control "public";
        }
复制代码

因为 html 类型的页面文件可能实时发版,不能设置较长时间的缓存,否则可能造成发版后不更新的现象。

静态资源都设置成较长缓存时间,由于发版后,如果静态资源有更新,都会产生新的 hash 值,从而使老的资源过期。

api 请求由于要实现获请最新内容,所以不设置浏览器缓存,期望的结果是能通过 etag 优化传输,但在实践中通过 nginx 设置 api 无效,具体过程还在探索中。

通过以上方式,对 web 程序的缓存进行了较精细的控制。

合理使用图片

网站本身使用的图片有两个来源,分别是 又拍云 和 我们自己的 idc(通过阿里云加速),在使用的过程中都没有裁切,导致整体加载的图片资源比较大,做了如下优化:

针对来自又拍云的图片资源

又拍云提供了动态裁切的功能,可以特别方便的控制图片资源。

例如对于某个图片资源,我们需要的是一个宽为 200px 的图片,以前我们是直接引用这个资源: domain.url/commodity/9… ,这是一个原始资源,可能是一个很大的图片,直接引用会造成浪费

通过自动裁切,我们可以引用指定宽度的图片: domain.url/commodity/9… ,通过在 url 后加 !/fw/200,可以引用宽度为 200px 的图片,最大限度上节省资源

对于支持 webp 的浏览器,还可以让其输出 webp 版本: domain.url/commodity/9… ,通过关键字 /format/webp 指定输出的资源格式为 webp

总的来说,又拍云提供方便灵活的资源控制方法,更多细节见官方文档: help.upyun.com/knowledge-b…

针对来自 idc 的图片资源

来自 idc 的图片相对较难处理,由于没有云存储提供的功能,idc 只是单纯的做为文件存储服务器,所以没有办法动态裁切。

不过天无绝人之路,可以通过 nginx 的一个模块来实现类似的功能,这个模块就是:ngx_http_image_filter_module,通过这个模块可以对指定的资源按条件进行裁切,当 CDN 回源的时候给他裁切好的图片就可以了,部分实现了云存储的功能,具体实施如下:

一、编译 nginx 时加上 --with-http_image_filter_module

二、配置 nginx

location ~* /images/(.+)$ {
        set $width -; #图片宽度默认值
        set $height -; #图片高度默认值
        if ($arg_width != "") {
            set $width $arg_width;
        }
        if ($arg_height != "") {
            set $height $arg_height;
        }
        #image_filter_jpeg_quality 85;
        image_filter resize $width $height; #设置图片宽高
        image_filter_buffer 10M;   #设置Nginx读取图片的最大buffer。
        image_filter_interlace on; #是否开启图片图像隔行扫描
        if ($arg_info = "yes") {
        #        image_filter size;
        }
        error_page 415 = 415.png; #图片处理错误提示图,例如缩放参数不是数字
    }
复制代码

通过以上配置,当我们想访问某个资源时可能通过: domain.url/images/img.… 得到宽为 200px 的图片。

由于大规模部署,运维需要做更详细的测试,所以当这次优化上线时,这个功能还没有上线。当测试完成后,运维就会将这个功能部署到线上。

这个模块的官方文档是: nginx.org/en/docs/htt…

使用图片懒加载 和 webp

使用图片懒加载 主要依赖 Vue-Lazyload 这个 npm 模块,具体使用方法见: www.npmjs.com/package/vue…

这里主要说一下其中的两个功能 progressive 和 webp:

Vue.use(VueLazyload,{
  observer: true,
  attempt: 10,
  filter: {
    progressive (listener, options) {
      const is_upyun_CDN = /upyunimages\./
      if (is_upyun_CDN.test(listener.src)) {
        listener.el.setAttribute('lazy-progressive', 'true')
        listener.loading = listener.src.replace(/fw.+/, 'fw/10')
      }
    },
    webp (listener, options) {
      if (!options.supportWebp) return
      const is_upyun_CDN = /upyunimages\./
      if (is_upyun_CDN.test(listener.src)) {
        listener.src += '/format/webp'
      }
    }
  }
})
复制代码

通过 filter 整体对所有懒加载资源进行过滤控制:

progressive:允许在加载大图见,先加载一个小图,会有一个很好的用户体验

webp: 对支持 webp 的浏览器,加载资源的 webp 版本,有效降低文件大小

通过这一系列操作,可以更进一种降低不必要的资源加载量。

升级至 webpack4

按说 webpack 3 用的好好的,为什么要升级到 4 呢,原因还是因为新版本给我们带来了诸多好片,而且目前已经是稳定版本,主要有以下内容:

  • 更快的编译速度,网上有说提高了 98%,我虽然没有验证数据,但直观上快了,而且快了很多
  • 零配置模块打包,虽然没办法做到“零” ,但是更多更合理的默认配置,使工程构建更加方便
  • 抛弃了 CommonChunksPlugin,使用更为先进的 SplitChunksPlugin 提取公共资源
  • 使用 Tree Shaking ,有效减少业务代码体积
  • 引入 mode 属性,可以定义为 development 和 production,不用再为生产和开发环境编写过多的配置程序
  • 等等其他未提及的 以及 默认的优化

升级过程中可能会遇到种种问题,好在有个官方升级指南 可以帮我们覆盖掉一部分,但这会指南过于简明扼要,具体到项目中还会有很多坑,好在通过错误提示结合伟大的google,最终都能找到答案(如果你用 baidu ,很有可能最终爬不上来 :< ...),也可以结合网上其他一些升级方面的文章,都会很有帮助,我这里就不细述了。

总在来说升级 wp 4 会花一些时间,但带来的效率和性能提升绝对值得。

优化首屏加载

对于 web 单页面应用而言,一个非常大的痛点就是在首次加载时加载的资源量过大,导致用户在第一次访问时出现在白屏时间较长,如何优化这个体验是整体优化中的重中之重,所以放在最后来说。

前面已经说了,通过减少图片大小、升级 webpack 4、优化公共资源包等等手段,都是为了这个服务(当然了,也不全是:>),这些手段都是对资源进行操作。当对这所有的资源进行了操作,如何合理处理这些资源,就到了浏览器的渲染机制,如何通过优化渲染过程,提高首屏渲染速度,是我们下一步要考虑的。

浏览器渲染页面的过程,主要分为五步(略过请求部分,只讨论请求到资源后浏览器如何处理):

  • 处理 HTML 标记并构建 DOM 树。
  • 处理 CSS 标记并构建 CSSOM 树。
  • 将 DOM 与 CSSOM 合并成一个渲染树。
  • 根据渲染树来布局,以计算每个节点的几何信息。
  • 将各个节点绘制到屏幕上。

从上面可知,浏览器只要加载到 html 结构和 css,就可以渲染出页面。

针对上面得到的结构,有3种可选方案来实现:

  • 服务端渲染
  • 利用 prerender-spa-plugin 做预渲染
  • HTML 内实现 Loading

先揭晓答案,最终我选择了第 3 种,至于为什么选择,接下来挨个来看

服务端渲染

什么是服务端渲染?来自官网 的解释是:将组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

服务器端渲染(SSR)的优势主要在于:

  • 更好的 SEO,由于搜索引擎爬虫抓取 工具 可以直接查看完全渲染的页面。
  • 更快的内容到达时间(time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。

我之前写过一篇文章,详细讨论了 如何实现一个服务端渲染项目

但是这样一个看似美好的方案,同样存在需要权衡的地方:

  • 开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数(lifecycle hook)中使用;一些外部扩展库(external library)可能需要特殊处理,才能在服务器渲染应用程序中运行。
  • 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序(SPA)不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
  • 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源(CPU-intensive - CPU 密集),因此如果你预料在高流量环境(high traffic)下使用,请准备相应的服务器负载,并明智地采用缓存策略。

同时还有一个不得不考虑的问题是,由于服务端渲染模糊了前后端的界限,需要更多的服务器方面的知识,在项目落地时要全面考虑运维、后期项目交接等等,最后放弃这个方案~

利用 prerender-spa-plugin 做预渲染

预渲染 可以达到和 SSR 类似的目的,它在编译阶段,将指定的页面编译成 html,当有请求时直接将 html 内容发送给客户端,但是他也存在问题:

  • 它是在编译阶段完成的,只能编译有限的页面(例如 /, /about, /contact 等),没有办法将所有内容静态化,而且如果内容有更新,之前编译过的页面也不会得到更新。
  • 页面抖动,例如我们首次访问的是一个动态内容 /article/id-1234 ,由于这个动态内容之前没有编译成 html,这个请求会落在 /index.html 上,index.html 会被先渲染,当动态接口有内容后会更新整个页面,渲染成目标页,这会对用记造成困惑。

事实上这是一个不错的方案,VUE 官方也推荐这个方案,在实践的过程中遇到了一些问题,我也做了一些记录 ,但是综合考虑还是放弃了这个方案。

HTML 内实现 Loading

这是我们最终选择的方案,这个方案从原理到实现都相对简单,它借助 html-webpack-plugin 将一段指定的 html 和 css 插入到模板中,在 js 和 api 请求未返回之前,以最快的速度给用户一个 loading 提示,告知用户得到了响应。

具体做法如下:

将 loading 效果的 html 拆分成 loading.htmlloading.css ,分别放在 /src/preLoad/loading.html 和 /src/preLoad/loading.css

在 webpack 的 config 文件里读取这两个文件:

module.exports = {
  loading: {
    html: fs.readFileSync(path.join(__dirname, '../src/preLoad/loading.html')),
    css: '<style>' + fs.readFileSync(path.join(__dirname, '../src/preLoad/loading.css')) + '</style>'
  }
  // ...
}
复制代码

在 build 的配置文件里引入:

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      loading: config.loading
      // ...
    })
    // ...
  ]
  // ...
}
复制代码

在模板文件中插入变量:

<!DOCTYPE html>
<html>
<head>
  <!-- ... -->
  <%= htmlWebpackPlugin.options.loading.css %>
</head>
<body>
  <div id="app">
    <%= htmlWebpackPlugin.options.loading.html %>
  </div>
</body>
</html>
复制代码

通过这种方法,可以将一个动态的 loading 效果插入到页面中。

由于 css 会阻塞渲染,所以当我们看到这个 loading 之前,尽量的少加载其他的 css 和 js,采用的方案是不提取 css,由于 vue-cli 默认的设置是提取,所以需要手动修改一下:

// vue-loader.conf.js
module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: false // 不提取
  }),
  // ...
}
复制代码

这样 css 就会被编译进 js 里,通过 js 进行输出,在 loading 效果出来之前,不会阻塞页面。

最后还有一个问题需要解决,由于程序依赖各种第三方包,这些包都会打包到 vendor.js 中,使这个文件特别大,甚至超过了 1M,需要对这块进行优化,思路就是告诉 webpack 要打包不,不要将某些包打到 vendor.js里,然后我们手动在 html 里引入这些文件。

由于现在第三方 CDN 提供了稳定的资源访问,而且借助 http/2 的多种利用特性,使的这些第三方资源加载特别快,具体做法如下:

在 webpack 的 config 定义将要从第三方引入的资源:

在 webpack 的基础配置文件里定义那些包不需要打包到 vendor.js里:

// webpack.base.conf.js
module.exports = {
  externals: {
    'vue': 'Vue',
    'vuex': 'Vuex',
    'iview': 'iview'
  }
  //...
}
复制代码

在 webpack 的 config 文件里批明第三方资源:

module.exports = {
  loading: {
    html: fs.readFileSync(path.join(__dirname, '../src/preLoad/loading.html')),
    css: '<style>' + fs.readFileSync(path.join(__dirname, '../src/preLoad/loading.css')) + '</style>'
  },
  css: [
    'https://cdn.jsdelivr.net/npm/iview@2.14.3/dist/styles/iview.css'
  ],
  js: [
    'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js',
    'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js',
    'https://cdn.jsdelivr.net/npm/iview@2.14.3/dist/iview.min.js'
  ]
  // ...
}
复制代码

和 loading 类似,在 build 的配置文件里引入:

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      loading: config.loading,
      externals_js: config.js, // 引入 js
      externals_css: config.css, // 引入 css
      // ...
    })
    // ...
  ]
  // ...
}
复制代码

在模板文件中插入变量:

<!DOCTYPE html>
<html>
<head>
  <!-- ... -->
  <%= htmlWebpackPlugin.options.loading.css %>
  <% for (var i in htmlWebpackPlugin.options.externals_css) { %>
  <link href="<%= htmlWebpackPlugin.options.externals_css[i] %>" rel="stylesheet">
  <% } %>
</head>
<body>
  <div id="app">
    <%= htmlWebpackPlugin.options.loading.html %>
  </div>
  

  <% for (var i in htmlWebpackPlugin.options.externals_js) { %>
  <script src="<%= htmlWebpackPlugin.options.externals_js[i] %>"></script>
  <% } %>
</body>
</html>
复制代码

通过这种方法,将比较大的包从 vendor.js 里剔除,通过第三方 CDN 引入。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

An Introduction to Probability Theory and Its Applications

An Introduction to Probability Theory and Its Applications

William Feller / Wiley / 1991-1-1 / USD 120.00

Major changes in this edition include the substitution of probabilistic arguments for combinatorial artifices, and the addition of new sections on branching processes, Markov chains, and the De Moivre......一起来看看 《An Introduction to Probability Theory and Its Applications》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试