Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

作者:NewName

https://juejin.cn/post/7273072756156235834

最近看了一篇前端性能优化的文章,之前也没搞过性能优化,就学习了一下。要想搞性能优化,得知道都有哪些和性能相关的指标,得知道有什么工具可以量化地评估网站的性能表现,还得知道怎么进行优化、优化的思路都有啥。学了一波,决定用 Lighthouse 这款性能测试工具来评估一下自己开发的网站的性能,优化前得分 56,优化后得分 96,怎么做的呢?阅读本文一起学习吧~


  1. Lighthouse 的使用 =================

Lighthouse 是由 Google 开发并开源的 Web 性能测试工具,通过监控和检测网站应用的各方面性能表现,为开发这提供优化用户体验和网站性能提供指导建议。

下面介绍两种使用 Lighthouse 的方式:通过 Chrome 插件使用和通过 Node CLI 使用。

1.1 通过 Chrome 插件使用

首先现在 Lighthouse 插件(Lighthouse 谷歌浏览器插件下载地址:https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk/related。

然后打开 Chrome 开发者工具,点击右上角的【三个点】,点击【更多工具】,再点击【性能监视器】:

然后点击【Lighthouse】选项卡,点击【分析网页加载情况】按钮:

如下为正在夹断网页得分:

几秒后,Lighthouse 性能分析报错生成:

可见性能分数比较低,具体指标得分如下:

点击【展开视图】可以了解每一指标的含义,可以点击连接了解每一指标的详情以及具体优化策略:

再往下可以查看优化建议:

再往下可以看诊断结果:

1.2 通过 Node CLI 使用

目前 Lighthouse 已经发布了 npm 包,可以在项目中集成:npm i lighthouse

如下为脚本代码:

const run = async () => {
  const browser = await puppeteer.launch({
    headless: "new",
    args: ["--no-sandbox", "--disable-setuid-sandbox"],
  });
  const page = await browser.newPage();
  const url = "你的网页url";
  await page.goto(url);
  const { port } = new URL(browser.wsEndpoint());
  const { report } = await lighthouse(url, {
    port,
    output: "html",
  });
  await writeFile("report.html", report);
  await browser.close();
};
run();

运行脚本即可得到 Lighthouse 生成的分析报告:

分析报告和浏览器插件方式得到在内容上是的一样,只不过是英文的形式。后续可以利用在此基础上进行改善和丰富,可以优化前端工程的部署流程。

有了性能分析报告,我们可以根据各性能指标得分,优化建议和诊断结果对项目进行优化,下面具体学习一下这些性能优化指标。

  1. 性能优化的指标 ==========

2.1 FCP ( First Contentful Paint)首次内容绘制

2.1.1 定义

首次内容绘制时间,测量页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间。

2.1.2 对定义的理解

所述页面内容必须是文本、图片(包含背景图),非白色的 canvas 或 SVG。

这是用户第一次看到页面的内容,注意是部分内容,并非所有内容。

如下图所示,FCP 发生在第二帧,因为那是首批文本和图像元素在屏幕上完成渲染的时间点:

如上图所示,虽然部分内容已完成渲染,但并非所有内容都已经完成渲染。这是首次内容绘制时间 (FCP) 与 * 最大内容绘制时间 (LCP,Largest Contentful Paint) 之间的重要区别。

2.1.3 评价标准

FCP 时间在 0-1.8 秒, 表示良好,颜色为绿色,FCP 评分将在 75~100 分;

FCP 时间在 1.9-3.0 秒, 表示需要改进,颜色为橙色,FCP 评分将在 50~74 分;

FCP 时间在 3.1 秒以上, 表示较差进,颜色为红色,FCP 评分将在 0~49 分。

2.1.4 缩短 FCP 时间的方法

指导方案可参照:如何改进 FCP [1],本文如何做的见下文。

2.1.5 注意事项

此指标对于没有使用 ssr 技术的 web 项目意义并不大,因为第一绘制发生的时间通常 JS 还没加载完毕。

2.2 LCP(Largest Contentful Pain)最大内容绘制

2.2.1 定义

最大内容绘制时间,根据页面首次开始加载的时间点来计算可视区域内可见的最大图像或者文本块完成渲染的相对时间。

2.2.2 对定义的理解

LCP 要考虑的元素包括:img 元素以及内嵌在 svg 元素内的 img 元素,video 元素,通过 url() 加载的背景图像元素,包含文本节点或者其他内联级文本元素的块级元素。

如下图所示,最大元素随内容加载而变化,随着新内容被添加进 DOM,并因此使最大元素发生了改变:

2.2.3 评价标准

LCP 时间在 0-2.5 秒, 表示良好,颜色为绿色;

LCP 时间在 2.6-4.0 秒, 表示需要改进,颜色为橙色;

LCP 时间在 4.1 秒以上, 表示较差进,颜色为红色。

2.2.4 缩短 LCP 时间的方法

指导方案可参考:如何改进 LCP[2],本文如何做的见下文。

2.2.5 注意事项

在某些情况下,页面上最重要的元素(或多个元素)并不是最大元素,而开发者可能更有兴趣测量前者的渲染时间(使用元素计时 API[3])。

2.3 TBT (Total Blocking Time)总阻塞时间

2.3.1 定义

总阻塞时间 (TBT) 指标测量 First Contentful Paint 首次内容绘制 (FCP) 与 Time to Interactive 可交互时间 (TTI) 之间的总时间。

2.3.2 对定义的理解

由定义可知:TBT 涉及到了 FCP 和 TTI 这两个概念, 对于 FCP 上文已经介绍过,这里补充一下 TTI(可交互时间的定义):TTI 指标测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。

2.3.3 评价标准

评价标准参加下表:

TBT 得分标准 [4]

为了提供良好的用户体验,网站在普通移动硬件上进行测试时,应当努力使 TBT 控制在 300 毫秒以内。

2.3.4 缩短 TBT 的方法

指导方案可参考:如何改进 TBT[5],本文如何做的见下文。

2.4 CLS(Cumulative Layout Shift)累积布局偏移

2.4.1 定义

CLS 测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数。

2.4.2 对定义的理解

累积布局偏移 (CLS) 是测量视觉稳定性的一个以用户为中心的重要指标,因为该项指标有助于量化用户经历意外布局偏移的频率,较低的 CLS 有助于确保一个页面是令人愉悦的。

2.4.3 评价标准

2.4.4 缩短 CLS 的方法

指导方案可参照:如何改进 CLS[6],本文不涉及。

2.5 SI(Speed Index)速度指标

2.5.1 定义

SI 是一个表示页面可视区域中内容的填充速度的指标。

2.5.2 对定义的理解

该指标捕获的是页面出现像素点的时间。

2.5.3 评价标准

评价标准参加下表:

2.5.5 缩短 SI 的方法

指导方案可参照:如何改进 SI[7],本文如何做的见下文。

  1. 具体的性能优化的方法 =============

文档快速加载总结了一些提升网站性能的技术,列出一下常用的方法:

  1. 消除阻塞渲染的资源: 例如对于引入三方的 script 标签加上 async 或者 defer。

  2. 缩小 CSS、移除未使用的 CSS: 例如使用压缩器压缩 CSS。

  3. 预连接到所需要的资源:例如使用<link rel="preconnect">通知浏览器,页面打算与另一个源建立连接,而且希望该过程尽快开始。

  4. 减少服务器的响应时间:例如使用 HTTP2。

  5. 使用缓存:例如使用 HTTP 缓存。

  6. 优化图片:例如压缩图片,使用 CDN, 延迟加载等。

  7. 删除未使用代码:例如删除未使用的库,删除不需要的库,删除无用代码,按需引入组件库。

  8. 减少 JS 负载:例如动态导入和代码拆分。

下面结合笔者的项目介绍一下笔者使用的方法:

3.1 使用 vite-compression-plugin

可以使用 vite-compression-plugin[8] 来对代码进行 gizp 压缩,使用方法如下:

vite.config.ts:

import viteCompression from 'vite-plugin-compression'
export default defineConfig({
  plugins:[
    viteCompression({
      ext: ".gz",
      algorithm: "gzip",
      deleteOriginFile: false
    })
  ]
})

关于插件的具体配置可以查看插件的文档,选择合适的压缩算法。

3.2 开启 ngnix 的 gizp 压缩

笔者的前端项目下有一个. devops 目录,下面有一个 ngnix.conf 文件可以单独配置此前端工程的 ngnix:

配置方法为在 server 下面增加如下配置:

server {
  gzip on;
  gzip_buffers 32 4K;
  gzip_comp_level 6;
  gzip_min_length 100;
  gzip_types application/javascript text/plain text/css text/xml application/json application/xml application/xml+rss;
  gzip_vary on;
  listen       80;
  location / {            
    # 此处省略location相关配置
  }
}

具体配置项含义以及取值,可以查询相关文档。

3.3 按需自动引入 element-plus

笔者项目原来是完整引入 element-plus 的, main.ts 文件内容:

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

这样会导致首页加载时被打包后的和 element-plus 相关的全部 js 和 css 资源被引入,但是按需引入时则不会一次性引入全部的 css 和 js。

按需引入 element-plus 的方法为:

第一步:安装 unplugin-auto-import 和 unplugin-vue-components:pnpm install unplugin-auto-import unplugin-vue-components

第二步:vite.config.ts 插件配置:

import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
  plugins:[
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      resolvers: [ElementPlusResolver()]
    }),
  ]
})

第三步:特殊插件的处理。对于对于命令式方式使用的组件,例如 ElNotification, 如果不单独引入,则会导致其样式失效,所以需要对项目中使用到的这类组件单独全局引入一下就 OK 啦:

import { ElInput, ElSelect, ElDatePicker, ElTimePicker } from "element-plus";
app.use(ElInput).use(ElSelect).use(ElDatePicker).use(ElTimePicker).use(directives).use(router).use(pinia).mount("#app");

3.4 修改百度地图的引入方式

原来的引入方式是在 html 文件中增加 script 标签:

<!DOCTYPE html>
<html lang="en">
  <body >
    <div id="app">
    <script type="module" src="/src/main.ts"></script>
    <script charset="utf-8" src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=***"></script>
  </body>
</html>

原来的使用方法如下:

const BMapGL: any = (window as any).BMapGL;
 const map = new BMapGL.Map("container");

改成使用时异步引用:

const LoadBaiduMapScript = () => {
  //console.log("初始化百度地图脚本...");
  const AK = "***";
  const BMap_URL = "https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=" + AK + "&s=1&callback=onBMapCallback";
  return new Promise(resolve => {
    // 如果已加载直接返回
    if (typeof (window as any).BMapGL !== "undefined") {
      resolve((window as any).BMapGL);
      return true;
    }
    // 百度地图异步加载回调处理
    (window as any).onBMapCallback =  () => {
      console.log("百度地图脚本初始化成功...");
      BMapGL = (window as any).BMapGL;
      resolve((window as any).BMapGL);
    };
    // 插入script脚本
    let scriptNode = document.createElement("script");
    scriptNode.setAttribute("type", "text/javascript");
    scriptNode.setAttribute("src", BMap_URL);
    document.body.appendChild(scriptNode);
  });
};

3.5 对图片进行压缩

这里推荐一个好用的压缩工具,熊猫压缩——tinypng 。

用它把 UI 给的切图、背景图等压缩一下,那必然也起到一定的作用。

笔者就通过如上五种优化方法对项目优化了一下,性能得分就得到了很大的改观,是不是觉得性能优化也不难,哈哈哈~

Node 社群

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

   “分享、点赞、在看” 支持一下