【前端性能测试】不服?那就跑个分!!!

avatar
Web前端 @CVTE_希沃

希沃ENOW大前端

公司官网:CVTE(广州视源股份)

团队:CVTE旗下未来教育希沃软件平台中心enow团队

本文作者:

小羽名片.png

hello,大家好,我是小羽同学。一个平凡,而又不甘于平凡的前端开发工程师!

大家对跑分应该挺熟悉了吧?尤其是米粉,雷老板的那句不服?那就跑个分!应该历历在目吧?哈哈哈

在日常的前端开发过程中,小伙伴们或多或少都有接触过性能优化吧?但是你平常是怎么确定性能是否得到了提升呢?Google的开源工具——Lighthouse也就应运而生。

Lighthouse主要是用于分析网络应用和网页,收集现代性能指标并提供对开发人员最佳实践的意见。对于前端开发工程师来说,可以简洁明了得看到项目中的不足之处,以及优化的方法。是日常开发中不可多得的神器呀!

简单的使用

lighthouse 插件安装

chrome的商店中,搜索lighthouse,然后添加。

设置测试项目

然后在chrome顶部的插件栏中打开插件。点击设置图标,还可以选择进行测试(跑分)的项目,以及是测试pc端还是手机端的网页。

测试(跑分)

然后我们直接点击generate report按钮即可直接进入测试,过一段时间后结果就会出来啦。下面那个图就是我们ENOW 大前端团队的掘金首页测试评分啦,可以看到掘金seo优化做的还是非常棒的,哈哈哈~

整个测试报告中给我们标明了页面中的各种性能相关的参数,然后也给到了我们很多相关的优化建议。小伙伴们觉得感兴趣的话,可以深入的去了解各个参数哦~

image.png

现在性能测试工具有是有了,但是有没有发现这个就只能一个一个页面的去测,好麻烦呀。如果有几十个页面还得一个一个的去点。你们会不嫌麻烦嘛?反正像小羽同学这么厌倦重复性工作的人来说,早就烦死了  (╯°Д°)╯︵┻━┻

现在问题来了,那有没有什么方便的方法呀?一个一个的去点着测试总不是方法呀。

别着急,干货马上就到啦~

其实除了chrome插件的使用方式,我们还可以使用命令行的方式来调用Lighthouse

1.全局安装 lighthouse

npm install -g lighthouse

2.输入你的页面

lighthouse http://test.com

小羽在这里就不进行展示了,咱们直接进入主题吧,嘿嘿~

制作跑分工具

我们要制作的前端性能跑分工具,主要是借助于lighthouse的这个npm包以及gulp脚本。

1.初始化项目

新建一个文件夹,小羽这里是enow-lighthouse,然后新建package.json并写入相关的内容,然后cnpm install安装我们编写工具时所需要的一些依赖包

{
  "name": "enow-lighthouse",
  "version": "1.0.0",
  "description": "ENOW大前端——lighthouse测试工具",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start":"gulp start"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chrome-launcher": "^0.13.4",
    "del": "^6.0.0",
    "fs-extra": "^9.1.0",
    "gulp": "^4.0.2",
    "lighthouse": "^7.3.0"
  }
}

2.编写gulp脚本

1.在根目录下创建gulpfile.js

然后在gulpfile.js中写入以下的一段代码,先测试一下我们的项目能不能正常跑起来。如果正常的话,则会显示如下图的ENOW 大前端~

const gulp = require("gulp")
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const printer = require('lighthouse/lighthouse-cli/printer');
const Reporter = require('lighthouse/lighthouse-core/report/report-generator');
const fs = require('fs-extra');
const del = require("del")
let chrome

gulp.task("start",async function(cb){
  console.log("ENOW 大前端")
  cb()
})

2.新增launchChrome方法,该方法是用来启动chrome的,并返回需要用到的chrome信息

  • --headless 表示不打开browser窗口
  • --disable-gpu 表示不开启gpu
  • --no-sandbox 表示不开启沙箱模式
// 开启chrome
async function launchChrome() {
  try {
      chrome = await chromeLauncher.launch({
          chromeFlags: [
              "--disable-gpu",
              "--no-sandbox",
              "--headless"
          ],
          enableExtensions: true,
          logLevel: "error"
      });
      return {
          port: chrome.port,
          chromeFlags: [
              "--headless"
          ],
          logLevel: "error"
      }
  } catch (e) {
      console.log("ENOW lighthouse error: launching Chrome ", e);
  }
}

3.新增lighthouseRunner方法,该方法是用来跑lighthouse测试的,并返回测试结果

// 启动lighthouse测试
async function lighthouseRunner(url, opt, config={extends: 'lighthouse:default'}) {
  try {
      return await lighthouse(url, opt, config);
  } catch (e) {
      console.log("ENOW lighthouse error: running lighthouse");
  }
}

4.新增genReport方法,该方法是用来获取当前页面报告的html页面,并返回生成的html页面

// 生成当前页面的报告
function genReport(result) {
  return Reporter.generateReport(result.lhr, 'html');
}

5.新增run方法,该方法是每个页面的测试入口

// 每个页面的测试入口
async function run(url, timestamp, config) {
  let chromeOpt = await launchChrome();
  let result = await lighthouseRunner(url, chromeOpt, config);
  let report = genReport(result);
  // 保存报告
  await printer.write(report, 'html', `./cases/lighthouse-report@${timestamp}.html`);
  // 关闭chrome
  await chrome.kill();
  return
}

6.修改gulp.task,然后在根目录下新建cases文件夹,然后运行npm run start等待一段时间后,就会发现在我们刚刚新建的cases文件夹中生成了我们想要的性能测试报告啦~

gulp.task("start",async function(cb){
  let taskList = [
    `https://juejin.cn/`,
    `https://juejin.cn/`,
    `https://juejin.cn/`,
  ]
  for(let item of taskList){
    let timestamp = Date.now();
    await run(item,timestamp)
  }
  cb()
})

小羽,你在这里洋洋洒洒的写了一大堆东西,咱看着脑壳都疼了!!!

其实这里看着挺多东西的,但是逻辑还是很容易理解滴~

待小羽给小伙伴们分析一波:

  • 首先我们运行npm run start就会调用gulp start,接着就进入到了gulp.task()
  • 然后gulp.task()是遍历taskList,然后调用run方法(每个页面的测试入口)
  • run方法调用launchChrome(),然后返回chrome相关信息
  • run方法调用lighthouseRunner(),然后返回测试结果
  • run方法调用genReport(),返回生成的html页面
  • run方法html页面写入到文件中
  • 关闭chrome

目前为止,gulpfile.js中的代码如下

/*
 * @Description: 
 * @Author: 小羽
 * @LastEditors: 小羽
 * @Date: 2021-04-11 23:05:22
 * @LastEditTime: 2021-04-12 00:30:55
 */
const gulp = require("gulp")
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const printer = require('lighthouse/lighthouse-cli/printer');
const Reporter = require('lighthouse/lighthouse-core/report/report-generator');
const fs = require('fs-extra');
const del = require("del")
let chrome

// 开启chrome
async function launchChrome() {
  try {
      chrome = await chromeLauncher.launch({
          chromeFlags: [
              "--disable-gpu",
              "--no-sandbox",
              "--headless"
          ],
          enableExtensions: true,
          logLevel: "error"
      });
      return {
          port: chrome.port,
          chromeFlags: [
              "--headless"
          ],
          logLevel: "error"
      }
  } catch (e) {
      console.log("ENOW lighthouse error: launching Chrome ", e);
  }
}


// 启动lighthouse测试
async function lighthouseRunner(url, opt, config={extends: 'lighthouse:default'}) {
  try {
      return await lighthouse(url, opt, config);
  } catch (e) {
      console.log("ENOW lighthouse error: running lighthouse");
  }
}

// 获取当前页面的报告
function genReport(result) {
  return Reporter.generateReport(result.lhr, 'html');
}

// 每个页面的测试入口
async function run(url, timestamp, config) {
  let chromeOpt = await launchChrome();
  let result = await lighthouseRunner(url, chromeOpt, config);
  let report = genReport(result);
  // 保存报告
  await printer.write(report, 'html', `./cases/lighthouse-report@${timestamp}.html`);
  // 关闭chrome
  await chrome.kill();
  return
}

gulp.task("start",async function(cb){
  let taskList = [
    `https://juejin.cn/`,
    `https://juejin.cn/`,
    `https://juejin.cn/`,
  ]
  for(let item of taskList){
    let timestamp = Date.now();
    await run(item,timestamp)
  }
  cb()
})

3.抽离任务列表

一般来说,我们的任务列表是不会直接写在方法中,为了符合低耦合高内聚编程的思路,我们单独把我们的任务列表抽离出来,然后使用require引入到gulpfile.js中。在根目录下新建taskList.js

当我们需要测某个页面的性能时,就可以把taskList.js全部改为同一个url就好。如果想测网站的整体性能,就把网站所有的URL拷贝进去就ok啦。

想知道怎么获取评分平均值以及总报告的小伙伴,请移步下一小节   (✧◡✧)

// taskList.js
module.exports = [
  `https://juejin.cn/`,
  `https://juejin.cn/`,
  `https://juejin.cn/`,
]
// gulpfile.js
const taskList = require("./taskList")

// 省略中间的代码。。。

gulp.task("start",async function(cb){
  for(let item of taskList){
    let timestamp = Date.now();
    await run(item,timestamp)
  }
  cb()
})

4.生成总报告

虽然说我们现在的工程也可以一键跑分了,但是有没有发现生成的文件很多,而且都得一个一个的点击进去才能看到我们的页面信息,可不可以?

stop,别问,问就是成妾做不到!!!

哈哈哈,逗下你们啦,程序员除了产品经理提出的需求完成不了外,其他时候都是超厉害滴【手动狗头】

修改一下代码,

npm run start启动跑分程序,输出一下lighthouse跑分后的结果来瞧瞧先。

在我们的根目录下就会生成一个file.txt文件。打开后一看。。。这啥玩意?完全没法看呀,这可咋办?

别急,山人自有妙计

打开我们原来生成的报表,稍微分析一下就会发现。file.txt中,lhr字段中输出的数据其实就是我们控制台中输出的数据。那就看控制台中的数据得了呗【狗头】

新增一个write()方法,功能是输出到文件中,这里是生成我们的总报告

// 生成总报告
async function write(file, report) {
  try {
      await fs.outputFile(file, report);
      return true
  } catch (e) {
      console.log("error while writing report ", e);
  }
}

修改run方法,run方法中返回的数据,小伙伴们想返回那些字段自己做抉择就ok啦~

async function run(url, timestamp, num, config) {
  let chromeOpt = await launchChrome();
  let result = await lighthouseRunner(url, chromeOpt, config);
  let report = genReport(result);
  // 保存报告
  await printer.write(report, 'html', `./cases/lighthouse-report@${timestamp}-${num}.html`);
  result.lhr.audits['first-contentful-paint'].rawValue;
  let res = {
      audits:{
          "first-contentful-paint":result.lhr.audits['first-contentful-paint']
      },
      categories:result.lhr.categories,
      lighthouseVersion:result.lhr.lighthouseVersion,
      requestedUrl:result.lhr.requestedUrl
  }
  // 关闭chrome
  await chrome.kill();
  return res;//result.lhr
}

根目录下新增summary/template/template.htmltemplate.html是我们的总报告模板文件,小羽这里也是随便写写,小伙伴们可以自由发挥~

修改gulp.task()

gulp.task("start",async function(cb){
  let timestamp = Date.now();
  let spent = [];
  console.log(`共 ${taskList.length} 个任务`)
  for (let i = 0; i < taskList.length; i++) {
    console.log(`当前第 ${i+1} 个任务`)
    spent.push(await run(taskList[i], timestamp, i));
  }
  // 替换模板中的内容
  let template = await fs.readFileSync('./summary/template/template.html', 'utf-8');
  let summary = Reporter.replaceStrings(template, [{
    search: '%%TIME_SPENT%%',
    replacement: JSON.stringify(spent)
  }, {
    search: '%%TIMESTAMP%%',
    replacement: timestamp
  }]);
  await write(`./summary/report/summary@${timestamp}.html`, summary)
  cb()
})

咳咳咳,提起精神来,看看我们的成果。

npm run start后,会发现在我们的summary/report中生成了一个新的html文件,咱们打开看下

5.添加PC端和移动端的命令

emmm,不对,还有问题,因为我们一直都是测的移动端,那我们怎么测试pc端呀???总不能每测试一次就去修改一次配置吧?

根目录下新增constants.jslighthouse-desktop-config.js(pc端)lighthouse-mobile-config.js(移动端)

既然把移动端和pc端分开了,那原来的gulp.task()也就只有一个,不够用了,那就多加一个吧。然后方便区分,再修改一下名字为create:report-desktopcreate:report-mobile。修改gulpfile.jspackage.json

// gulpfile.js
const desktopConfig = require('./lighthouse-desktop-config.js');
const mobileConfig = require('./lighthouse-mobile-config.js');


// 省略部分代码。。。

gulp.task('create:report-desktop',async function(cb){
  let timestamp = Date.now();
  let spent = [];
  console.log(`共 ${taskList.length} 个任务`)
  for (let i = 0; i < taskList.length; i++) {
    console.log(`当前第 ${i+1} 个任务`)
    spent.push(await run(taskList[i], timestamp, i , desktopConfig));
  }
  // 替换模板中的内容
  let template = await fs.readFileSync('./summary/template/template.html', 'utf-8');
  let summary = Reporter.replaceStrings(template, [{
    search: '%%TIME_SPENT%%',
    replacement: JSON.stringify(spent)
  }, {
    search: '%%TIMESTAMP%%',
    replacement: timestamp
  }]);
  await write(`./summary/report/summary@${timestamp}.html`, summary)
  cb()
})

gulp.task('create:report-mobile',async function(cb){
  let timestamp = Date.now();
  let spent = [];
  console.log(`共 ${taskList.length} 个任务`)
  for (let i = 0; i < taskList.length; i++) {
    console.log(`当前第 ${i+1} 个任务`)
    spent.push(await run(taskList[i], timestamp, i, mobileConfig));
  }
  // 替换模板中的内容
  let template = await fs.readFileSync('./summary/template/template.html', 'utf-8');
  let summary = Reporter.replaceStrings(template, [{
    search: '%%TIME_SPENT%%',
    replacement: JSON.stringify(spent)
  }, {
    search: '%%TIMESTAMP%%',
    replacement: timestamp
  }]);
  await write(`./summary/report/summary@${timestamp}.html`, summary)
  cb()
})
// package.json

{
  "name": "enow-lighthouse",
  "version": "1.0.0",
  "description": "ENOW大前端——lighthouse测试工具",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "create:report-desktop":"gulp create:report-desktop",
    "create:report-mobile":"gulp create:report-desktop"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chrome-launcher": "^0.13.4",
    "del": "^6.0.0",
    "fs-extra": "^9.1.0",
    "gulp": "^4.0.2",
    "lighthouse": "^7.3.0"
  }
}

此时,我们已经没有了start命令了,所以启动的命令就变成了npm run create:report-desktopnpm run create:report-mobile

6.删除旧的测试文件

但是小伙伴们有没有发现我们的报表文件每次跑都会遗留下来,越积越多呀?

gulpfile.js中新增三个gulp.task()。然后修改package.json中命令

  • npm run mobile:清理文件,然后执行移动端跑分
  • npm run desktop:清理文件,然后执行pc端跑分
  • npm run clean:清理文件
  • npm run create:report-mobile:执行移动端跑分
  • npm run create:report-desktop:执行pc端跑分

这里简单的说一下,在gulp中,gulp.series()是按照顺序执行的,每次执行一个。而gulp.paralle(),则是并发运行的。

// gulpfile.js
// 清理数据
gulp.task('clean:report', function (cb) {
  del([
      'cases/**/*',
      'summary/report/**/*',
  ], cb);
  cb()
});

// gulp.series:按照顺序执行
// gulp.paralle:可以并行计算
gulp.task("start-desktop", gulp.series("clean:report","create:report-desktop"), function () {})
gulp.task("start-mobile", gulp.series("clean:report","create:report-mobile"), function () {})
// package.json
{
  "name": "enow-lighthouse",
  "version": "1.0.0",
  "description": "ENOW大前端——lighthouse测试工具",
  "main": "index.js",
  "scripts": {
    "mobile":"gulp start-mobile",
    "desktop":"gulp start-desktop",
    "clean":"gulp clean:report",
    "create:report-desktop":"gulp create:report-desktop",
    "create:report-mobile":"gulp create:report-desktop"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chrome-launcher": "^0.13.4",
    "del": "^6.0.0",
    "fs-extra": "^9.1.0",
    "gulp": "^4.0.2",
    "lighthouse": "^7.3.0"
  }
}

好了,现在我们整个跑分工具就制作完成啦。如果有小伙子说他写的网页有多厉害,那就二话不说,掏出这个跑分工具,一决高下吧~

后语

本文主要是结合Google的开源项目Lighthousegulp脚本编写了一个前端性能跑分工具。主要用来帮助前端开发工程师能够更加全面的了解自己的网站/项目,快速找出优缺点,以及可以改善的方向。