基于 VUE-SSR 的性能优化

基于 VUE-SSR 的性能优化

关于 SSR(全称 Server-side-render),每一个前端同学一定都很熟悉,我们知道 SSR 可以减少白屏等待时间,对 SEO 友好,容易被搜索引擎抓取到,但是我们该怎么写好一个 SSR 项目呢?下面这篇文章由一道著名的面试题为起点,带你一步一步揭开 SSR 的奥秘。

著名面试题:从浏览器中输入 URL 发生了什么。

这个过程简单概括为几大步:

  1. DNS 解析
  2. TCP 连接
  3. 发送 HTTP 请求
  4. 服务器处理请求并返回 HTTP 报文
  5. 浏览器解析渲染页面

作为一个前端工程师我们应该关注 3、4、5。

浏览器发送 HTTP 请求前,会首先检查该资源是否存在缓存,有以下请求头、响应头作为缓存标识:Expires、Cache-Control、Last-Modified、if-Modified-Since、Etag、if-None-Match,下面来给他们分个类。

缓存分为强缓存、协商缓存两种

强缓存

当浏览器准备发送 Http 请求请求一条资源时,它检查之前曾经发过这条资源,而且这条资源当时的响应结果带了 Expires 这个响应头并设置了一个绝对的时间 Expires: Wed, 21 Oct 2021 00:00:00 GMT,这个时候浏览器一看,这条资源到 2021 年才过期呢,就不会发送请求了,而是直接取之前的返回结果。
Expires 是 http1.0 时代的强缓存依据,在 http1.1 又补充了 Cache-Control 这个响应头作为强缓存依据,Cache-Control 的通常用法是 Cache-Control: max-age=31600,它表示资源有效时间,是一个相对的时间。Cache-Control 的存在解决了当服务器时间和客户端时间(浏览器的时间实际上是依赖系统时间的,而我们是能够随意修改系统时间的)不一致引发的问题,我们发出的 http 资源的强缓存依然有效,不会时间变长也不会变短。

协商缓存

  • Last-Modified If-Modified-Since (http1.0)
    一个请求响应时会返回一个响应头 Last-Modified 表示这个资源上次修改的时间,下次相同的资源请求会带上 If-Modified-Since 这个请求头,值和上次的 Last-Modified 相同,服务端通过判断 If-Modified-Since 这个时间是否是当前资源的最后修改时间来决定是否使用缓存。如果可以使用缓存就会以 304 状态码返回。
    这个方式有着和 Expires 响应头类似的弊病:强依赖系统时间,当系统时间发生变化时,这个 Header 所带来的缓存是否过期的信息将无法被信赖。
  • Etag If-None-Match (http1.1)
    和上面的过程非常类似,服务端第一次返回资源时会生成一个资源的摘要,作为响应头 Etag 返回给客户端。
    客户端请求时通过 If-none-match 带上之前 Etag 的值,服务端来比较 If-none-match 和当前资源的摘要是否一致来判断是否命中缓存。

Webpack 与浏览器缓存的配合

通过浏览器缓存机制我们可以极大的减少浏览器请求的资源量,加快页面的展现。在现代前端项目中,浏览器缓存机制往往是配合 Webpack 来实现的,我们一般通过 Webpack 对项目进行打包。在 Webpack 中核心配置主要有 entry、output、module、plugin,通过以下最基础的配置来对 Webpack 配置有一个基础的印象。

const path = require('path')
const { ProgressPlugin } = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin-webpack4')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const handler = (percentage, message, ...args) => {
  console.info('构建进度:', percentage)
}
module.exports = {
  mode: 'production', // 可选值有 'node' || 'development' || 'production' production 会设置 process.env.NODE_ENV = 'production' 并开启一些优化插件
  entry: './main.js', // Webpack 打包开始的入口文件
  output: {
    // 完成打包后的输出规则
    path: path.resolve(__dirname, 'dist/'), // 输出到当前目录的 dist 目录下
    filename: '[name].[hash].js' // 文件会按照 [name].[hash].js 的命名规则进行命名
  },

  /**
   * Webpack 只能够解析 Js 模块,当遇到非 Js 的模块、文件时,需要通过 loader 将其转换成 Js
   */

  module: {
    rules: [
      { test: /\.vue$/, use: 'vue-loader' }, // 将 Vue 文件转换为 html、css、js 三部分
      {
        test: /\.(css|less)$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] //  Less => Css => Js => 最后利用 MiniCssExtractPlugin.loader 抽离出 css 文件。
      },
      {
        test: /\.js$/,
        use: ['babel-loader'] // 利用 babel 将 ES 高版本代码编译为 ES5
      }
    ]
  },
  /**
   * Loader 的存在能够让 Webpack 识别并转换任何的代码,但是缺少在打包过程中对资源进行操作的方式,Plugin 通过 Webpack 内置的钩子函
   * 数给我们提供了强大的扩展性,我们可以利用 Plugin 做很多事情。
   */

  plugins: [
    new CleanWebpackPlugin(), // 在打包前清空 output 的目标目录
    new VueLoaderPlugin(), // 配合 Vue-loader 使用,将 Vue-loader 转换出 Js 代码重新根据 rules 配置的 loader 进行转换
    new HtmlWebpackPlugin({
      // 利用指定的 html 模版在构建结束后生成一个 html 文件并引入构建后资源。
      template: 'index.html'
    }),
    new MiniCssExtractPlugin({
      // 将本来内置在 Js 的样式代码抽离成单独的 css 文件
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css'
    }),
    new ProgressPlugin(handler) // 打印构建进度
  ]
}

通过以上 Webpack 配置构建后构建的结果如下所示
基于 VUE-SSR 的性能优化_第1张图片
在构建结果中我们能够看到,我们的输出的文件都按照根据本次构建的 hash 生成了一个文件名称中带有 hash 的文件,利用这个 hash 我们可以使用浏览器的强缓存,通过配置 Cache-Control: max-age={很大的数字}来是我们的静态资源能够保留在浏览器中,当下一次构建时会生成新的 hash,不会因为缓存而导致 Web 应用无法更新。

想要学习 Webpack,可以看一看 Webpack 文档,如果想要深入的学习 编写一个插件 是不可错过的。

利用 CDN 对静态资源进行加速

CDN 全称是 Content Delivery Network(内容分发网络),它的作用是减少传播时延,找最近的节点。通过以上缓存的方式我们解决了重复请求资源的效率问题,但是当第一次请求资源时,这好几 Mb 的内容够用户加载好一会儿了,如果都是从服务器中发出,可想而知服务器的贷款压力有多大。
CDN 的存在帮我们解决了这个问题,CDN 的主要作用就是减轻源站(服务器)负载,通过部署在全球各地的节点返回数据。真正的 CDN 可能在某个地区的运营商都会有一个专门的节点。
基于 VUE-SSR 的性能优化_第2张图片

我们将内容上传至 CDN 源站中,当第一次访问该资源的时候会进行 DNS 查询获得该域名的 CNAME 记录,然后对新的 CNAME 进行 DNS 查询会得到一个离用户访问最近的边缘服务器的 IP 地址,用户浏览器与边缘服务器建立 TCP 链接,将 HTTP 请求发送到边缘服务器,边缘服务器检查是否有该资源,如果没有该资源会进行回源,向上一级 CDN 服务器请求该资源,直至找到该资源并返回给边缘服务器,边缘服务器会缓存该资源,并返回给用户。当另一个用户访问到同一个边缘服务器时,就能很快的获取该资源。

Vue-Spa 使首屏加载变慢

在解释为何 Vue-Spa 使首屏加载变慢前我们首先需要了解当浏览器请求到资源后是如何渲染资源的。

  1. 浏览器请求到 HTML 文档并构建文档对象模型(DOM)
  2. 浏览器加载样式文件,构建层叠样式表模型(CSSOM)
  3. 在 DOM 和 CSSOM 构建后会生成渲染树,包括一切将要被渲染的对象,除了 display:none等不可见的标签
  4. 根据渲染树会进行 layout 过程,确定每个元素的位置、大小等,并最终调用操作系统绘制在显示屏幕上。

如果我们直接请求一个 html 文件就是上面的过程,这个过程非常快,在几毫秒就可以完成。

但是得力与前端技术的发展,我们开发的大型 WEB 应用无法通过一个 Html 就能传给用户使用,我们在 Html 中引入了很多很多 Javascript 文件,并通过 Javascript 来渲染我们的应用。以 Vue-Spa 为例我们重新讲解这个渲染过程。

  1. 浏览器请求到 HTML 文档并构建文档对象模型(DOM)
  2. 浏览器加载样式文件,构建层叠样式表模型(CSSOM)
  3. 在 DOM 和 CSSOM 构建后会生成渲染树,包括一切将要被渲染的对象,除了 display:none等不可见的标签
  4. 根据渲染树会进行 layout 过程,确定每个元素的位置、大小等,并最终调用操作系统 API 绘制在显示屏幕上。

浏览器依然会走以上这四步过程,但是因为我们的 html 中除了一些

你可能感兴趣的:(前端,javascript,vue.js,性能,node.js)