前言
目前哈啰前端应用质量监控使用到了 Lighthouse 作为首页性能问题定性检查的工具,在使用过程中,我们基于 Lighthouse Plugin 对其原有能力进行扩展,用于检测 web 端和小程序的性能问题,在实践中也积累了些经验,希望能通过本文的分享,给大家一些帮助。
Lighthouse 简介
Lighthouse 是一个开源的自动化检测工具,通过内置审计模块,分析 web 应用和 web 页面的性能指标,最终给出页面的首屏得分和最佳实践指南。
使用方式
使用方式有四种,具体可以参考官方使用说明(github.com/GoogleChrom…) ,启动姿势很重要,大家选一款适合自己的。
模块实现
整体架构图如上,模块的实现细节可以参考 Lighthouse 模块实现。 Lighthouse Plugin 主要会涉及到 Gatherers、Audits、Categories 这三个模块。
检测报告
Lighthouse 的检测报告默认展示 5 类信息:
- Performance(性能)
- Progressive Web App(渐进式 web 应用)
- Accessibility(无障碍)
- Best Practices(最佳实践)
- SEO(SEO)
可以通过调用不同 config 来展示其中几类。
Lighthouse Plugin 开发入门
可以参考官方文档 ,这里给出一个简单的示例。开发一个plugin,主要分为以下两步:
一、新建 Lighthouse Plugin项目 创建一个 npm 包,包含 plugin.js 和 package.json:
module.exports = {
// 这里可以新增自己的 audit(审计项)
audits: [{path: 'lighthouse-plugin-example/test-audit.js'}],
// 这里可以设置自定义分类的一些信息,比如标题、描述、引用的审计项等
category: {
title: 'title',
description: 'description',
auditRefs: [{id: 'test-id', weight: 1}],
},
};
package.json
{
"name": "lighthouse-plugin-example",
"main": "plugin.js",
"peerDependencies": {
"lighthouse": "^5.6.0"
},
"devDependencies": {
"lighthouse": "^5.6.0"
}
}
注:Lighthouse 要按 peerDependencies 方式引入,避免重复下载。
由于上例用到了新的审计项,所以我们也需要新建这个文件
const { Audit } = require('lighthouse');
class TestAudit extends Audit {
static get meta() {
return {
// 这里定义审计项的一些配置,比如标题、异常描述、依赖的artifacts等
requiredArtifacts: ['devtoolsLogs'],
};
}
static audit(artifacts) {
// lighthouse 运行过程会经过一系列的采集器(gatherers),
// 每个采集器都会收集自己的目标信息,并生成中间产物(artifacts)
return {
score: 1,
};
}
}
module.exports = TestAudit;
二、使用Lighthouse Plugin
如果是命令行,直接运行:
如果是 npm 方式运行, Lighthouse 的配置需改为:
extends: 'lighthouse:default',
plugins: ['lighthouse-plugin-example'],
settings: {
// ...
},
};
Lighthouse Plugin 在项目中的实践
先聊一聊为什么要做 哈啰前端应用质量监控的规划之一,就是统一使用Lighthouse 做多端的首屏性能的定性检查。浏览器端是天然支持的,难点是小程序,无法直接在浏览器中直接运行,也就无法使用 Lighthouse 进行定性检查。所幸,现在有 taro、antmove 等一众的开源工具,可以借助这些工具将小程序转换为运行在浏览器端的应用,如此一来,我们可以将 Lighthouse 集成到我们的工程体系中来,使用相同的工具链对浏览器端和小程序端进行定性检查。
不凭空的制造需求,是商业公司技术团队的立足之本。在整体目标的引导下,我们要考虑如何来检查小程序的性能,传统意义上的Web页面的检查方式是否可以直接搬过来呢?答案是否定的,而对于小程序的技术实现原理,Lighthouse默认那一套不能直接照搬。运行在浏览器端的小程序,FMP、FCI、TTI等都可以做横向对比,但其结果的可靠性还要进行推敲,除此之外,我们需要有一套符合小程序技术特性的分析方法和工具,我们需要开发对应的Lighthouse Plugin,对应的Gatherers、Audits和Categories。
这张截图来自支付宝小程序的部分检测项,在传统 web 端检测的基础上,多了许多小程序特有检测(比如 setData 频率、数据量、API 调用次数、同/异步调用等),这些指标目前 Lighthouse 并非天生就覆盖到。
为小程序定制一个插件
以下以编写一个审计支付宝小程序Native API调用次数的插件为例,简单的总结如下:
业务代码注入采集逻辑 + gatherer 收集采集数据 + audit 消费数据并计算结果
业务代码注入采集逻辑
定义一个日志采集对象window.nativeCall,通过调用window.nativeCall.push 采集一次调用行为,示例代码如下。
window.$$nativeCall = {
calls: [],
push: function (type, callInfo) {
this.calls.push({ timestamp: Date.now(), type, callInfo });
},
};
function $$myproxy(my) {
return new Proxy(my, {
get(target, key) {
const keyValue = target[key];
// 如果是API方法,则代理原方法
if (typeof keyValue === 'function') {
return $$myProxyFn(keyValue, target, key);
}
// 否则,直接上报了一个API属性的调用检测点
window.$$nativeCall.push('api-attr-called', JSON.stringify({
key: `my.${key}`,
}));
return keyValue;
}
});
};
function $$myProxyFn(fn, that, key) {
return (function (...args) {
// 上报了一个AP方法的调用检测点,携带自定义参数
window.$$nativeCall.push('api-method-called', JSON.stringify({
key: `my.${key}`,
args,
}));
try {
const result = fn.apply(this, args);
// 上报了一个AP方法的成功调用的检测点,叫 api-method-success-called
window.$$nativeCall.push('api-method-success-called', JSON.stringify({
key: `my.${key}`,
args,
result: err,
}));
} catch (err) {
// 上报了一个AP方法的异常调用检测点,叫 api-method-error-called
window.$$nativeCall.push('api-method-error-called', JSON.stringify({
key: `my.${key}`,
args,
result: err,
}));
}
}).bind(that);
}
示例代码写的比较简单,只是将调用信息存储在了window.$$nativeCall中。这段代码需要通过编译工具链来插入到小程序的业务代码中,以拦截小程序代码中所有的Native API的调用。
自定义 audit 审计模块
在进行审计分析之前,我们需要一个 Gatherer 来获取window.$$nativeCall采集到的信息。
const { Gatherer } = require('lighthouse');
class CustomNativeCall extends Gatherer {
public async afterPass(passContext) {
const { driver } = passContext;
const $$nativeCall = await driver.evaluateAsync('window.$$nativeCall');
if ($$nativeCall) {
return $$nativeCall.calls;
}
return [];
}
}
module.exports = CustomNativeCall;
收集器写好了。我们需要编写 Audit 的处理逻辑,这里,我们以统计 Native API 调用次数(api-method-called)为例:
const { Audit } = require('lighthouse');
class ApiMethodCalledAudit extends Audit {
static get meta() {
return {
// 定义好 id,这样 plugin.js 就能找到这个审计项
id: 'api-method',
// 这里引入上面定义好的收集器
requiredArtifacts: ['CustomNativeCall'],
};
}
static audit(artifacts) {
const { CustomNativeCall } = artifacts;
const apiMap = {};
CustomNativeCall.forEach((log) => {
const {
// timestamp, // 时间戳在这个审计项中没用到,可以不声明
type,
callInfo,
} = log;
// 判断type是否是我们上报的特定类型:api-method-called
if (type === 'api-method-called') {
const { key } = JSON.parse(callInfo);
// 取出api的方法名,计数
apiMap[key] = apiMap[key] || 0;
apiMap[key] += 1;
}
});
const result = Object.keys(apiMap).reduce((res, apiKey) => {
res.push({
api: apiKey,
count: apiMap[apiKey],
});
return res;
}, []);
return {
// 生成表格明细
details: Audit.makeTableDetails(ApiMethodCalledAudit.getHeadings(), result),
// 结果展示文案
displayValue: `共找到 ${result.length} 次API方法调用`,
// 评分,区间在0-1,1表示该审计项满分
score: 1,
};
}
private static getHeadings() {
return [
{
itemType: 'text',
key: 'api',
text: '名称',
},
{
itemType: 'numeric',
key: 'count',
text: '调用次数',
},
];
}
};
module.exports = ApiMethodCalledAudit;
最后,在我们的 plugin.js 引入 api-method-called-audit.js
module.exports = {
audits: [{path: 'lighthouse-plugin-example/api-method-called-audit.js'}],
category: {
title: '容器',
description: 'description',
// 这里注入plugin用到的audit依赖,id取自audit里的meta.id
auditRefs: [{id: 'api-method', weight: 1}],
},
};
至此,数据上报、采集、消费整个流程便打通了。
小程序中的实践总结 以下整理了当前我们实际项目中定义的部分日志类型、审计模块。
日志类型
- api-attr-called:用于 API 属性访问次的相关统计
- api-method-error-called:用于 API 方法的异常调用次数的统计
- api-method-called:用于 API 方法的调用次数情况统计
- api-method-success-called:用于 API 方法的成功调用次数、耗时的统计
- set-data:用于 setData 调用次数、数据大小的统计
- set-data-success:用于 setData 调用耗时统计
Audit
- api-async-same-args-called: 异步 API 方法相同入参调用
- api-attr-called: API 属性调用
- api-deprecated-called: 废弃 API 调用
- api-duplicate-called: API 被重复调用情况(连续 20 次)
- api-error-called: API 异常调用
- api-long-time-called: API 调用耗时过长(超过 1000ms)
- api-method-called: API 方法调用
- api-sync-called: API 同步方法调用情况
- page-node-used: 文档节点复杂度
- set-data-called: setData 调用频繁(20 次/秒)
- set-data-size: setData 数据量超限(超过 256kb)
Category
我们将审计项分为三大类:
- performance
主要包含性能相关的审计项,比如setData调用次数、setData单次设置的数据量、页面节点数等等,这些审计项的优化,可以带来相对直观的性能提升。
- container
主要包含容器相关的审计项,比如小程序api(异常、重复、耗时、相同入参)调用、小程序native属性调用等等,这些审计项和小程序运行环境直接关联。
- best-practice
主要包含最佳实践相关的审计项,比如图片转webp、禁止废弃api调用、请求异常处理、同步转为异步api调用等等,这些审计项都和官方推荐的开发方式相关。
插件使用方法
以 npm 包方式为例,我们将 Lighthouse 的配置改为:
module.exports = {
extends: 'lighthouse:default',
plugins: [
// 这里引入了容器检测,性能和最佳实践如果需要的话也可以在这里引入
'lighthouse-plugin-miniprogram/plugins/container'
],
passes: [{
passName: 'defaultPass',
gatherers: [
'lighthouse-plugin-miniprogram/gatherers/custom-native-call',
],
}],
settings: {
// ...
},
};
运行结果
这里我们用目前已经写好的统计项作为演示,使用方式和上例一致的。
最后
本文从实际业务出发,简要介绍了 Lighthouse在哈啰前端应用质量监控中的运用,对于如何在小程序中应用好Lighthouse,我们目前也还在探索阶段,欢迎交流。
参考资料
- Lighthouse-developers(文章链接:developers.google.com/web/tools/l…
- Lighthouse 测试内幕(文章链接:juejin.im/post/5dca05…
- Web 性能优化地图(文章链接:github.com/berwin/Blog…
- Chrome DevTools Protocol(文章链接:chromedevtools.github.io/devtools-pr…
- Lighthouse-architecture(文章链接:github.com/GoogleChrom…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!