# 网站性能测量和优化方法

## [**以用户为中心的性能指标**](https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics?hl=zh-cn)

网页的性能，以及网页应用的运行速度**不能仅用加载时间来评估**。加载时间会因为用户不同而有很大的变化，具体取决于用户的设备功能以及网络状况。

但事实是，随时都有可能发生性能不佳的情况，不只限于加载期间。 应用无法迅速响应点按或点击操作，以及无法平滑滚动或产生动画效果的问题与加载缓慢一样，都会导致糟糕的用户体验。

\*\*用户关心的是总体体验，\*\*我们开发者也应如此。

所有这些性能误解有一个共同的主题，即开发者都将注意力集中在对于用户体验帮助不大甚至全无帮助的事情上。 同样地，[加载](https://developer.mozilla.org/en-US/docs/Web/Events/load)时间或 [DOMContentLoaded](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) 时间等传统性能指标极不可靠，因为加载发生的时间可能与用户认为的应用加载时间对应，也可能不对应。

当用户导航到网页时，通常会寻找视觉反馈，以确信一切符合预期。

|             |                       |
| ----------- | --------------------- |
| **是否发生？**   | 导航是否成功启动？服务器是否有响应？    |
| **是否有用？**   | 是否已渲染可以与用户互动的足够内容？    |
| **是否可用？**   | 用户可以与页面交互，还是页面仍在忙于加载？ |
| **是否令人愉快？** | 交互是否顺畅而自然，没有滞后和卡顿？    |

为了解页面何时为用户提供这样的反馈，我们定义了多个新指标：

* **首次绘制与首次内容绘制**。

  [Paint Timing](https://github.com/WICG/paint-timing) API 定义两个指标：*首次绘制* (FP) 和 *首次内容绘制* (FCP)。 这些指标用于标记导航之后浏览器在屏幕上渲染像素的时间点。 这对于用户来说十分重要，因为它回答了以下问题： **是否发生？**

  这两个指标之间的主要差别在于，FP 标记浏览器渲染任何在视觉上不同于导航前屏幕内容之内容的时间点。 相比而言，FCP 标记的是浏览器渲染来自 DOM 第一位内容的时间点，该内容可能是文本、图像、SVG 元素。
* **首次有效绘制和主角元素计时**

  首次有效绘制 (FMP) 指标能够回答“**是否有用？**”这一问题。 虽然“有用”这一概念很难以通用于所有网页的方式规范化（因此尚不存在任何规范），但是网页开发者自己很清楚其页面的哪些部分对用户最为有用。

  网页的这些最重要部分通常称为主角元素。
* **耗时较长的任务**

  对于用户而言，任务耗时较长表现为滞后或卡顿，而这也是目前网页不良体验的主要根源。
* **可交互时间**

  可交互时间\* (TTI) 指标用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点。 应用可能会因为多种原因而无法响应用户输入：

  * 页面组件运行所需的 JavaScript 尚未加载。
  * 耗时较长的任务阻塞主线程（如上一节所述）。

  TTI 指标可识别页面初始 JavaScript 已加载且主线程处于空闲状态（没有耗时较长的任务）的时间点。

下表概述刚刚列出的各个指标如何对应到我们希望优化的体验：

| 体验      | 指标                      |
| ------- | ----------------------- |
| 是否发生？   | 首次绘制 (FP)/首次内容绘制 (FCP)  |
| 是否有用？   | 首次有效绘制 (FMP)/主角元素计时     |
| 是否可用？   | 可交互时间 (TTI)             |
| 是否令人愉快？ | 耗时较长的任务（在技术上不存在耗时较长的任务） |

## 性能分析工具

### [PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights/)

PageSpeed Insights 能够针对移动设备和桌面设备生成网页的实际性能报告，并能够提供关于如何改进相应网页的建议。

### [gtmetrix](https://gtmetrix.com/)

查看您网站的效果，揭示网站运行缓慢的原因，并发现优化机会。

通过配置的监视来跟踪页面性能，并通过交互式图形将其可视化。

* **监控页面**并每天，每周或每月进行测试以确保最佳性能
* **使用3个可用的图表可视化性能**：页面加载时间，页面大小和请求计数以及PageSpeed和YSlow得分
* **缩放，平移和设置日期范围**以查找特定的性能历史记录
* 在图形上**注释感兴趣的区域**并提供数据上下文
* 根据PageSpeed / YSlow得分，页面加载时间，总页面大小等，在**多种情况下设置警报**
* **用户注册后，可以设置cookie解决登录问题**

## 数据统计工具

### [Google Analytics（分析）使用入门](https://support.google.com/analytics/answer/1008015?hl=zh-Hans\&ref_topic=3544906)

### [Omniture](https://lizh.gitbook.io/knowledge/research/wang-zhan-xing-neng-ce-liang-he-you-hua-fang-fa)

### [大前端神器安利之 Puppeteer](https://www.jeffjade.com/2017/12/17/134-kinds-of-toss-using-puppeteer/)

\*\*实现：\*\*在node中引用puppeteer，获取网页的performance.timing，将数据存入mysql。

设置cookie解决登录问题

```js
const browser = await puppeteer.launch({
  devtools: true,
  headless: false,
  defaultViewport: {
    width: 375,
    height: 667
  }
})
const browserPage = await browser.newPage()
await browserPage.setCookie({
	name: 'token',
	value: '',
	domain: '.abc.com',
	path: '/',
	expires: 1640995200000,
	httpOnly: false,
	secure: false,
	sameSite: "Strict"
});
```

**chrome 网速预设值**

| Preset     | download(kb/s) | upload(kb/s) | RTT(ms) |
| ---------- | -------------- | ------------ | ------- |
| GPRS       | 50             | 20           | 500     |
| Regular 2G | 250            | 50           | 300     |
| Good 2G    | 450            | 150          | 150     |
| Regular 3G | 750            | 250          | 100     |
| Good 3G    | 1000           | 750          | 40      |
| Regular 4G | 4000           | 3000         | 20      |
| DSL 2000   | 1000           | 5            |         |
| WiFi 30000 | 150000         | 2            |         |

### [Lighthouse](https://lavas-project.github.io/pwa-book/appendix01/2-lighthouse-score-guide.html)

使用 Lighthouse 对网站进行测评后，我们会得到一份评分报告，它包含了性能（Performance），PWA（Progressive Web App），访问无障碍（Accessibility），最佳实践（Best Practice），搜索引擎优化（SEO）等几个部分。

**影响评分的性能指标**

性能测试指标分成了 Metrics，Diagnostic，Opportunities 三部分。通常情况下，只有 Metrics 部分的指标项会对分数产生直接影响，Lighthouse 会衡量以下性能指标项：

* **首次内容绘制（First Contentful Paint）**

  即浏览器首次将任意内容（如文字、图像、canvas 等）绘制到屏幕上的时间点。

  [算法-首次有效渲染时间(Time to First Meaningful Paint)](https://segmentfault.com/a/1190000009870748)
* **首次有效绘制（First Meaningful Paint）**

  衡量了用户感知页面的主要内容（primary content）可见的时间。对于不同的站点，首要内容是不同的，例如：对于博客文章，标题及首屏文字是首要内容，而对于购物网站来说，图片也会变得很重要。
* **首次 CPU 空闲（First CPU Idle）**

  即页面首次能够对输入做出反应的时间点，其出现时机往往在首次有效绘制完成之后。该指标目前仍处于实验阶段。
* **可交互时间（Time to Interactive）**

  指的是所有的页面内容都已经成功加载，且能够快速地对用户的操作做出反应的时间点。该指标目前仍处于实验阶段。
* **速度指标（Speed Index）**

  衡量了首屏可见内容绘制在屏幕上的速度。在首次加载页面的过程中尽量展现更多的内容，往往能给用户带来更好的体验，所以速度指标的值约小越好。
* **输入延迟估值（Estimated Input Latency）**

  这个指标衡量了页面对用户输入行为的反应速度，其基准值应低于 50ms。

## 性能优化方案

### [懒加载(动态加载)](https://vuejscaff.com/articles/56/load-components-on-demand-when-using-vuejs)

懒加载或者按需加载，是一种很好的优化网页或应用的方式。尤其是内容较多的页面。**减少页面初始化时，需要加载的JS文件体积**

这种方式实际上是先把你的代码在一些逻辑断点处分离开，然后在一些代码块中完成某些操作后，立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度，减轻了它的总体体积，因为某些代码块可能永远不会被加载。

```js
export default {
  ...
  components: {
    Abc: () => import('./abc.vue')
  },
  ...
}
```

<https://juejin.im/post/59bf501ff265da06602971b9>

[Vue路由和组件的懒加载](https://www.jianshu.com/p/6fb92ea1790d)

### [按需加载](https://juejin.im/post/5b35c49bf265da598f156446)

使用`babel-plugin-component`实现按需引入、打包。**过滤无用模块，减少JS文件体积**

修改.babelrc配置：

```js
//.babelrc
{
  ...
  "plugins": [
    "transform-runtime",
    [
      "component", [
        {
          "libraryName": "mint-ui",
          "style": true
        }, {
          "libraryName": "bxs-ui-vue",
          "libDir": "lib",
          "style": "index.css"
        }
      ]
    ]
  ],
  ...
}
```

### [外部扩展(externals)](https://webpack.docschina.org/configuration/externals/)

**防止**将某些 `import` 的包(package)**打包**到 bundle 中，而是在运行时(runtime)再去从外部获取这些*扩展依赖(external dependencies)*。

```js
//html文件引入js
<script src="//assets.winbaoxian.com/vue/2.5.17/vue${process.env.NODE_ENV === 'production' ? '.min' : ''}.js"></script>

//webpack.base.conf.js
module.exports = {
  ...
  externals: {
    vue: 'Vue'
  },
  ...
}
```

### [静态方件上传到CDN](https://git.winbaoxian.com/wy-front/fed-knowledge-share/issues/42)

```js
//webpack.prod.conf.js
const AliOSSPlugin = require('webpack-alioss-plugin')
webpackConfig.plugins.push(new AliOSSPlugin({
  project: '', // 项目名(用于存放文件的直接目录)
}))
```

## 骨架屏

### [page-skeleton-webpack-plugin](https://github.com/ElemeFE/page-skeleton-webpack-plugin)(饿了么)

Page Skeleton 是一款 webpack 插件，该插件的目的是根据你项目中不同的路由页面生成相应的骨架屏页面，并将骨架屏页面通过 webpack 打包到对应的静态路由页面中。

有以下特点：

* 支持多种加载动画
* 针对移动端 web 页面
* 支持多路由
* 可定制化，可以通过配置项对骨架块形状颜色进行配置，同时也可以在预览页面直接修改骨架页面源码
* 几乎可以零配置使用

测试结果（lizh）：

* 骨架元素的结构跟实际的页面有差异
* 骨架页面元素的大小无法自适应

  即无法参数配置`html` 的`fontSize`值
* 只适用vue-router项目（单页面项目）

  注入的js是根目录的，不是当前页面的；且生成文件路径的跟现有不一样(planbookInput项目)
* 不适用页面内容不固定的页面

  比如：有些页面需要根据URL中的传参，展示不同内容。生成骨架屏时无法预知

### [vue-skeleton-webpack-plugin](https://github.com/lavas-project/vue-skeleton-webpack-plugin)(百度)

基于 Vue 的 webpack 插件，为单页/多页应用生成骨架屏 skeleton，减少白屏时间，在页面完全渲染之前提升用户感知体验。

有以下不足：

* 需要手动去写骨架屏的样式
* 骨架屏样式在不同尺寸下的响应式问题
* 在界面改动之后也需要手动修改对应的骨架屏
* 骨架页面的组件无法热更新 (官方说法是已经支持了，没找到原因)

### [draw-page-structure](https://github.com/famanoder/dps#readme)(京东)

提供两种方法来盛放生成的骨架屏节点：

* 配置 `output.filepath`，如果配置的是目录，会写入到该目录里的 `index.html` (没有的话我们会创建)文件里；
* 自定义写入的方式 `writePageStructure: (outputHtml: string) => void;`；

如果前面两种方式您都没有提供，那么将会在您当前目录下创建 `index.html` ，并将骨架屏节点写入；

有以下不足：

* 登录问题，解决方法不是很友好 ([查看解决方法](https://github.com/famanoder/dps/issues/12))。
* 生成的样式，不太美观

## Vue SSR 服务端渲染

### 什么是服务器端渲染 (SSR)？

Vue.js 是构建客户端应用程序的框架。默认情况下，可以在浏览器中输出 Vue 组件，进行生成 DOM 和操作 DOM。然而，也可以将同一个组件渲染为服务器端的 HTML 字符串，将它们直接发送到浏览器，最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

服务器渲染的 Vue.js 应用程序也可以被认为是"同构"或"通用"，因为应用程序的大部分代码都可以在**服务器**和**客户端**上运行。

这里说的渲染，就是指生成 HTML 文档的过程。简单来说，

**浏览器端渲染**，指的是用 JS 去生成 HTML，例如 React, Vue 等前端框架做的路由。

**服务器端渲染**，指的是用后台语言通过一些模版引擎生成 HTML，例如 Java 配合 VM 模版引擎、NodeJS配合 Jade 等，将数据与视图整理输出为完整的 HTML 文档发送给浏览器。

![](https://user-gold-cdn.xitu.io/2017/2/21/5cdfb8833f5bfb146178cd8446572338?imageslim)

### 为什么使用服务器端渲染 (SSR)？

与传统 SPA (单页应用程序 (Single-Page Application)) 相比，服务器端渲染 (SSR) 的优势主要在于：

* 更好的 SEO，由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

  请注意，截至目前，Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。在这里，同步是关键。如果你的应用程序初始展示 loading 菊花图，然后通过 Ajax 获取内容，抓取工具并不会等待异步完成后再行抓取页面内容。

  也就是说，如果 **SEO 对你的站点至关重要，而你的页面又是异步获取内容**，则你可能需要服务器端渲染(SSR)解决此问题。
* 更快的内容到达时间 (time-to-content)，特别是对于缓慢的网络情况或运行缓慢的设备。

  无需等待所有的 JavaScript 都完成下载并执行，才显示服务器渲染的标记，所以你的用户将会更快速地看到完整渲染的页面。通常可以产生更好的用户体验，并且对于那些「内容到达时间(time-to-content) 与转化率直接相关」的应用程序而言，服务器端渲染 (SSR) 至关重要。

使用服务器端渲染 (SSR) 时还需要有一些权衡之处：

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

### 是否真的需要使用服务器端渲染 (SSR)？

在对你的应用程序使用服务器端渲染 (SSR) 之前，你应该问的第一个问题是，是否真的需要它。

这主要取决于内容到达时间 (time-to-content) 对应用程序的重要程度。例如，如果你正在构建一个内部仪表盘，初始加载时的额外几百毫秒并不重要，这种情况下去使用服务器端渲染 (SSR) 将是一个小题大作之举。然而，内容到达时间 (time-to-content) 要求是绝对关键的指标，在这种情况下，服务器端渲染 (SSR) 可以帮助你实现最佳的初始加载性能。

### 如何看一个网页是否是服务器端渲染？

简单的方式是在 Chrome 浏览器打开控制台/开发者工具，查看 Network 中加载的资源。浏览器端渲染，html文档的body节点中只有一个div节点和css/js文件引用。其他节点是在浏览器中，js生成的。

## 参考链接

[蚂蚁金服如何把前端性能监控做到极致?](https://www.infoq.cn/article/Dxa8aM44oz*Lukk5Ufhy)

[一种自动化生成骨架屏的方案](https://github.com/Jocs/jocs.github.io/issues/22)

[前端骨架屏方案小结](https://juejin.im/post/5bc5396ee51d456f490984eb)

[Vue SSR 指南](https://ssr.vuejs.org/zh/)

[带你走近Vue服务器端渲染（VUE SSR）](https://juejin.im/post/5b72d3d7518825613c02abd6)
