最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Lighthouse 在小程序中的实践

    正文概述 掘金(HelloQ)   2021-04-10   772

    前言

    目前哈啰前端应用质量监控使用到了 Lighthouse 作为首页性能问题定性检查的工具,在使用过程中,我们基于 Lighthouse Plugin 对其原有能力进行扩展,用于检测 web 端和小程序的性能问题,在实践中也积累了些经验,希望能通过本文的分享,给大家一些帮助。

    Lighthouse 简介

    Lighthouse 是一个开源的自动化检测工具,通过内置审计模块,分析 web 应用和 web 页面的性能指标,最终给出页面的首屏得分和最佳实践指南。

    使用方式

    使用方式有四种,具体可以参考官方使用说明(github.com/GoogleChrom…) ,启动姿势很重要,大家选一款适合自己的。

    模块实现

    Lighthouse 在小程序中的实践

    整体架构图如上,模块的实现细节可以参考 Lighthouse 模块实现。 Lighthouse Plugin 主要会涉及到 Gatherers、Audits、Categories 这三个模块。

    检测报告

    Lighthouse 在小程序中的实践 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。

    Lighthouse 在小程序中的实践 这张截图来自支付宝小程序的部分检测项,在传统 web 端检测的基础上,多了许多小程序特有检测(比如 setData 频率、数据量、API 调用次数、同/异步调用等),这些指标目前 Lighthouse 并非天生就覆盖到。

    为小程序定制一个插件

    以下以编写一个审计支付宝小程序Native API调用次数的插件为例,简单的总结如下:

    业务代码注入采集逻辑 + gatherer 收集采集数据 + audit 消费数据并计算结果

    业务代码注入采集逻辑

    定义一个日志采集对象window.nativeCall,通过调用window.nativeCall ,通过调用 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,我们目前也还在探索阶段,欢迎交流。

    参考资料

    • 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…

    起源地下载网 » Lighthouse 在小程序中的实践

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元