pikou1995 / pikou1995.github.io

My Github Page
4 stars 1 forks source link

FCP优化指南 #12

Open pikou1995 opened 3 years ago

pikou1995 commented 3 years ago

在一系列测试网络应用质量的工具中,Lighthouse 脱颖而出。并且它集成进了 chrome 中,便利性也很高。

在 Lighthouse6 中,FCP 是六个主要指标中第一个指标,它测量的是用户第一次访问网页时浏览器需要多长时间呈现第一块 DOM 内容。图片,非空白的<canvas>,SVG 被认为是 DOM 内容(不包括 iframe 内的)。也不会考虑浏览器缓存的情况。FCP 在 v6 版本比重占 15%(v5 占 20%),是其他指标Speed IndexLargest Contentful PaintTime to Interactive的基础,合计占总评分的70%,是开始优化的第一步。

我自己写了一个 demo,其中总结了自己的优化经验,记录一个典型的 webpack + antd 的项目中怎么进行优化。下面的文章也是基于这个demo。

介绍demo

这是一个典型的网站,总共分为四个页面,包含了一些常见的组件,分别为主页,表单,表格和图表。样式和 Network 瀑布流如下:

样式和Network瀑布流

使用Lighthouse

这里使用的 Chrome 版本是86.0.4240.111,Lighthouse 版本是6.2.0。

Lighthouse工具

等demo启动好,打开开发者工具 Lighthouse 面板,设置好需要评估的选项,这里以Performance和Desktop为例,表示测试桌面端浏览器的性能。点击 Generate report 开始生成报告。第一次启动可能时间会久一些,Lighthouse 需要预热。稍作等候,这样就得到了我们的报告。

初始报告

可以看到这里的瓶颈就是 FCP 2.5s

Lighthouse 模拟了环境带宽和 CPU 性能,是为了统一标准和计算结果,并不代表目标用户的实际体验。 Lighthouse 模拟环境

优化打包大小

webpack打包结果

我们知道同步 js 的加载和执行是会阻碍 DOM 渲染的, css 资源的优先级也是最高的,所以我们要把不需要的资源移除,减少打包的大小。下面我们借助 webpack-bundle-analyzer 分析打包结果。

webpack-bundle-analyzer 结果

鼠标悬浮在模块上会显示详情:stat size 是引用源码的大小,parsed size 是经过压缩后的大小,gzipped size 是 gzip 后的大小。面积越大在文件中的比例就越大。

可以看到所占比例最大的是 echarts,其次是 moment。我们查找相关文档,echarts 可以按需引入,moment 不需要的 locale 文件也可以移除。

按需引入

// src/components/chart.tsx
// 完整引入
import echarts from 'echarts';

// 按需引入
import echarts from 'echarts/lib/echarts'
import 'echarts/lib/chart/line'
import 'echarts/lib/chart/pie'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'

完整引入大小:完整引入 按需引入大小:按需引入

可以看到改成按需引入后结果是让人惊喜的。除了 echarts,现在很多开源组件都支持了按需引入,有的是通过 ES modules 支持来 tree shaking。其他依赖也可以找到对应的文档,查看是否提供了按需引入的方法。

antd 的样式按需引入

antd 本身支持 ES modules的 tree shaking 的,样式也可以只引入需要的部分。

// 完整引入
// src/app.tsx
// 入口处完整引入样式
import 'antd/dist/antd.css'

// 按需引入
// src/app.tsx
// 入口处移除全部样式
- import 'antd/dist/antd.css'
// src/components/home.tsx
import { Carousel } from 'antd'
// 引入需要的样式
import 'antd/es/carousel/style/index.css'

但是这样太繁琐了,我们可以交给 babel-plugin-import 处理。只需按需引入 js 的部分,它会自动帮我们引入组件对应的 css。

// .babelrc
{
  // ...
  "plugins": [
    // ...
    [
      "import",
      { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }
    ]
  ]
}

css完整引入:css完整引入 css按需引入:css按需引入

只引入需要的 moment.js locales

antd 部分组件依赖了moment.js,使用 webpack 打包时会把 moment.js 和所有的 locales 都引入。我们可以使用 moment-locales-webpack-plugin 来移除不需要的 locales。

// webpack-config.js
const MomentLocalesPlugin = require('moment-locales-webpack-plugin')

module.exports = {
  // ...
  plugins: [new MomentLocalesPlugin({
      localesToKeep: ['zh-cn'], // 保留需要的语言
  })],
};

引入了所有的locales:引入了所有的locales只引入需要的locales:只引入需要的locales

如果想进一步优化,可以用 day.js 替换 moment.js

代码压缩

webpack 在 production模式 下默认启用 TerserPlugin 来精简 js,但是 css 还没压缩。下面我们用 css-minimizer-webpack-plugin 来压缩 css。

// webpack-config.js
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
      new CssMinimizerPlugin(),
    ],
  },
};

css压缩前:css压缩前 css压缩后:css压缩后

阶段性成果

阶段性成果

FCP成绩的提高,其他指标也随之提高。

组件懒加载

打开首页的时候,其他三个页面的组件也一并打包进了 main.js 和 main.css,从而会占用带宽和 CPU 资源,拖慢首页的加载。我们可以使用 webpack 的动态加载组件 import()React.lazy 来实现代码拆分和懒加载。

Form组件为例:

// 同步加载
import Form from './components/form'
<Route path="/form">
  <Form />
</Route>

// 懒加载
import { Suspense, lazy } from 'react'
import { Spin } from 'antd'

const Form = lazy(
  () =>
    import(
      /* webpackChunkName: "form" */
      './components/form'
    )
)
<Route path="/form">
  <Suspense fallback={<Spin />}>
    <Form />
  </Suspense>
</Route>

懒加载 FCP这时候 FCP 从 1.6s 降到了 1.3s。点击 View Original Trace 查看 perfermence:

懒加载 perfermence

可以看到浏览器只请求了首页的 main.js 和 main.css,而 Form 的资源要等到 url 变成 /form 才会去请求。

代码拆分打包结果

此时 main.css 和 main.js 优化减少到了image,而 Form 和其他组件对应的 js 和 css 都从 main 里面拆分了出来。

vue 框架也支持 AsyncComponent来实现懒加载

gzip 进一步优化资源下载速度

在 demo 里我们给静态服务加上 gzip,把 koa-static 替换成 koa-static-cache,默认以 level=6 压缩。

由于 koa-static-cache 不支持 index.html,所以我们需要加上 index.html,即通过 http://localhost:8080/index.html 访问。

优化完成

compression-webpack-plugin 可以在打包的时候就生成 gzip 后的资源,可以根据实际情况选择对应的压缩方式。

总结

实际上用户的体验可能会因为网络通畅,资源缓存而更好,也可能因为网络拥堵,服务器压力大等情况更差。 但我们经过了下面的几个方向的尝试,确实提高了web应用的质量:

当然这只是茫茫多优化中的一小部分而已,再好的程序总是会有优化的空间的。良好的用户体验不仅仅需要速度快,还要浅显易懂的设计,友善的引导,清晰的出错提示等等。

其他可以优化或需要注意的地方

CDN

CDN 服务商一般都能提供更快的网络和更低的延迟,让用户就近获取资源,缩短资源下载时间,减少自身服务器的压力。而且CDN 可以避免和 api 共用同一个域名,浏览器请求时不会携带 cookie 等 http 头,减少请求时间。

浏览器并发请求链接数

一般现代浏览器在同一个 origin 同时最大链接数是 6。打开多个标签也共享这6个限制。html,css,font,js,图片,ajax,下载文件等 http 请求等都受到限制( websocket 除外)。

有些应用依赖项过多的,想使用 DllPluginsplitChunks.cacheGroups 等功能拆分 js 文件的需要注意,同步的 js 拆出来加载时依然是同步的,如果超过最大链接数反而会成为瓶颈。可以考虑转换成动态引入 import()

Server Side Rendering(SSR)

服务端渲染是个值得尝试的技术,配合 css 的按需加载,可以让浏览器在 js 加载和执行前就渲染出页面。使用时要注意做好缓存和用户隔离。

HTTP/2

HTTP/2其中的特性比如 HTTP 头压缩, 服务端推送, 多路复用等,可以减少资源传输的时间。HTTP/2 的支持率达到了 96% 以上

可能会遇到的问题

为什么 Lighthouse 评分极低?

除了网站可能没有遵循最佳实践,也可能是 Lighthouse 版本小于 v6,在 v5 中 CPU 是4x slowdown并且模拟了更高的延迟和更低的吞吐量。

为什么 webpack-bundle-analyzer 结果没有 parsed size 和 gziped size?

可能你使用了 webpack5,笔者自测 webpack-bundle-analyzer@3.9.0 版本还没有完全适配 wepack5。