Lighthouse 流程架构

原由

前段时间紧急上线了一个门户项目,两端静态页面,首页考虑到须要极致体验必须使用硬编码搭建,部分子页面采用可视化搭建,要求Lighthouse必须接近满分,尽管经过一些手段优化了首屏但上线以后,离目标还有一大段误差。因而去挖lh源码关注各种指标对分值的影响程度,有了针对性的方向,剩下的工做就简单的多。css

顺便整理了源码。html

LightHouse流程架构

Lighthouse 是一个开源的自动化工具,提供了Node、Chrome Extension App、Chrome DevTool 三端,经过输入审查网址及配置项,经过一系列模拟测试特定环境下的运行情况和性能分析,最后生成性能结果页面供可视化浏览,是前端领域的安兔兔、鲁大师。前端

为何须要Lighthouse?一直以来,前端性能的分析指标过于泛化,得不到有效统一的标准,特别是近几年SPA、微服务、小程序、Flutter、webAssembly、***、ServerLess等前端技术方案百花齐放,获得高速发展的同时,一些传统的性能测量指标和方式落后跟不上脚步,没法支撑现有技术体系和新领域的迭代更新,再加上终端环境复杂、用户体验标准难以衡量、兼容性问题,审计指标愈来愈复杂。
例如阿里云ARMS针对 SPA 应用的FMP计量方式改为了依赖于MutationObserve计算权重变化最大的时间节点;淘宝前端团队的秒开率标准;岳鹰结合jssdk与Android内核查看聚集绘制指令来判断页面是否处于白屏....都代表在大前端趋势不可逆转,而测量性能的方式须要考虑更多环境和因素,变得越发复杂。git

Lighthouse 必定不是大前端下性能统计标准,由于从目前而言仍只适用于web端,而且其统计的指标过于笼统。自己而言依赖于 DevTool 发送回来的综合报告按 Audit 分析,输出对应的抽象分数、核心点和优化项,分数低不同表明性能差,但分数高必定是性能上佳。github

总体流程

image.png

名词释义

Driver

根据 Chrome Debugging Protocol <URL>与浏览器交互的对象web

Gatherers

驱动 Driver 收集到的网页基础信息,用于后续 Auditing 的审计逻辑。chrome

Artifacts

一系列 Gatherers 信息集合。在 Auditing 里会被附加其余信息,被多个Audits共享。json

Audits

以指定依赖的 Artifacts 做为输入,测试单个功能/优化/指标,审计测试评估分数,获得一组LHAR(LightHouse Audit Result Object) 标准数据对象。小程序

Report

ReportRender 使用LHR结果建立输出的UI报表。promise

基本概念

Lighthouse 驱动 Driver 经过 Chrome DevTool Protocol 与浏览器交互,执行一系列命令,先生成 Gatherers 模块用以收集 Artifacts 信息,这些 Artifacts 信息的聚合会在 Auditing 阶段做为 Audit case 逻辑的输入凭证,经过定义的一系列自定义的审计标准输出分数/优化/详情/描述/缘由/展现形式/错误等信息,最终获得一系列LHR统计结果,按需生成指定文件。

基本经常使用的命令以下,具体命令就不贴了

文档传送门

$ lighthouse --helplighthouse <url> <options>

Logging:
  --verbose  是否显示详细的日志  [boolean] [default: false]
  --quiet    不显示进度、调试、错误日志  [boolean] [default: false]

Configuration:
  --save-assets                  将跟踪内容和 devTools 日志保存到磁盘  [boolean] [default: false]
  --list-all-audits              打印全部审计列表内容  [boolean] [default: false]
  --list-trace-categories        打印全部必需跟踪类别的列表  [boolean] [default: false]
  --print-config                 输出规范化的配置  [boolean] [default: false]
  --additional-trace-categories  跟踪并捕获附加类别 (逗号分隔).  [string]
  --config-path                  JSON配置路径 lighthouse-core/config/lr-desktop-config.js
  --preset                       应用内置配置,与config-path冲突, [choices: "perf", "experimental", "desktop"]
  --chrome-flags                 自定义flag 空格区分,省略则默认使用 Chrome桌面版或者金丝雀版,all flag List: https://bit.ly/chrome-flags
  --port                         调试协议端口,0表示随机  [number] [default: 0]
  --hostname                     调试协议的hostname  [string] [default: "localhost"]
  --form-factor                  审计的模式,桌面/无线端  [string] [choices: "mobile", "desktop"]
  --screenEmulation              设置模拟屏幕的参数. 见--preset, 使用 --screenEmulation.disabled 以禁用. 不然默认: --screenEmulation.mobile --screenEmulation.width=360 --screenEmulation.height=640 --screenEmulation.deviceScaleFactor=2
  --emulatedUserAgent            设置用户UA  [string]
  --max-wait-for-load            设置最大的加载时间,以审计较完整的过程,过大会致使评分审计方式误差  [number]
  --enable-error-reporting       启用错误报表覆盖偏好配置. --no-enable-error-reporting 相反. More: https://git.io/vFFTO  [boolean]
  --gather-mode, -G              从交互的浏览器收集artifact保存到磁盘. 
  --audit-mode, -A               处理磁盘上保存的 artifacts. 默认 ./latest-run/
  --only-audits                  仅执行指定的审计项  [array]
  --only-categories              仅测量指定的功能: accessibility, best-practices, performance, pwa, seo  [array]
  --skip-audits                  跳过指定的审计项  [array]

Output:
  --output       报表输出格式 "json", "html", "csv"  [array] [default: ["html"]]
  --view         经过浏览器打开报表  [boolean] [default: false]

Options:
	--extra-headers                      调试额外的HttpHeaders
  --precomputed-lantern-data-path      模拟数据的文件路径, 覆盖对服务器延迟和RTT,能够下降受网络层面的影响.  [string]
  --lantern-data-output-path           基于`precomputed-lantern-data-path` 输出文件的路径.  [string]
  --plugins                            执行指定插件  [array]
  --channel  													 通道 [string] [default: "cli"]
  --chrome-ignore-default-flags  			 忽略掉浏览器默认的flag [boolean] [default: false]

Examples:
  lighthouse <url> --view                                                                          报表生成打开浏览器预览
  lighthouse <url> --config-path=./myconfig.js                                                     自定义配置
  lighthouse <url> --output=json --output-path=./report.json --save-assets                         保存跟踪、截图、JSON报表
  lighthouse <url> --screenEmulation.disabled --throttling-method=provided --no-emulatedUserAgent  禁用设备模拟和限流
  lighthouse <url> --chrome-flags="--window-size=412,660"                                          启用特定size窗口
  lighthouse <url> --quiet --chrome-flags="--headless"                                             启用无头浏览器及忽略全部日志
  lighthouse <url> --extra-headers "{\"Cookie\":\"monster=blue\", \"x-men\":\"wolverine\"}"        request添加请求头
  lighthouse <url> --extra-headers=./path/to/file.json                                             request添加JSON请求头
  lighthouse <url> --only-categories=performance,pwa                                               只测量Performance和PWA项

For more information on Lighthouse, see https://developers.google.com/web/tools/lighthouse/.复制代码

Download Repo 到本地,运行

lighthoust https://xixikf.com复制代码
从入口开始

在入口处 lighthouse-cli/bin.js 收集命令行 cliFlags 生成配置,收集和合成配置完,生成flags以下,
image.png
runLighthouse 负责唤起 ChromeLauncher 和调用 lighthouse 。

  let launchedChrome; // 浏览器实例

  try {const shouldGather = flags.gatherMode || flags.gatherMode === flags.auditMode;// 启动浏览器实例if (shouldGather) {
      launchedChrome = await getDebuggableChrome(flags);
      flags.port = launchedChrome.port; // 原flags port可能会被占用,chromelauncher会自动更新}// 执行 lighthouse-core 核心逻辑,拿到 LHRconst runnerResult = await lighthouse(url, flags, config);// 仅执行 gatherMode 策略,不会有runnerResult, 须要额外保存.if (runnerResult) {      await saveResults(runnerResult, flags);
    }		// 测量结束杀掉 Chrome 进程await potentiallyKillChrome(launchedChrome);    // ...// 有错误直接退出,不但愿用让用户看到if (runnerResult && runnerResult.lhr.runtimeError) {     // ...}return runnerResult;
  } catch (err) {// 过程出错,杀死进程退出await potentiallyKillChrome(launchedChrome).catch(() => {});return printErrorAndExit(err);
  }复制代码

流程概览

核心逻辑主要分五步

  • 生成 Runner Options,即准备须要测量的各功能/优化/指标项与调试配置
  • 经过 ChromeProtocol 协议约定 hostname/port 创建链接进行通讯,获取到Connection实例
  • 执行 Runner 逻辑生成 Driver 控制 Connection 实例发送交互命令,执行Collect主流程
    • 建立 Tab 后应用并预配置参数。
    • 对 passes 遍历每一个 pass 的 Gatherers 实例,调用对应 lifecycle 拿到 GatherersResult。
  • 将 GathererResult 传递给 Audits,遍历 Audits case,导入依赖执行审计逻辑最终输出标准LHR对象。
  • LHR对象JSON化并统计各种 Categories 分值,根据配置偏好输出到本地。
async function lighthouse(url, flags = {}, configJSON, userConnection) {  // 设置日志级别,通常状况吐出info
  flags.logLevel = flags.logLevel || 'error';
  log.setLevel(flags.logLevel);  // configJSON: Lighthouse 运行配置,flags: 可选配置
  const config = generateConfig(configJSON, flags);  const options = { url, config };  const connection = userConnection || new ChromeProtocol(flags.port, flags.hostname);  const gatherFn = ({requestedUrl}) => { // 第3/4/5步return Runner._gatherArtifactsFromBrowser(requestedUrl, options, connection);
  };  return Runner.run(gatherFn, options);
}复制代码

生成Runner Options

假设没传入 configJSON 文件,将默认使用 default-config.js 。setting

// lighthouse-core/config/defaultConfig.jsconst defaultConfig = {
  setting,	audits: [ // 主要的审计项'is-on-https', // 是否使用了https'service-worker', // 是否包含SW'metrics/first-contentful-paint', // fcp 首次内容绘制'metrics/largest-contentful-paint', // lcp 最后内容绘制'metrics/first-meaningful-paint',  // fmp 首次主要内容绘制'metrics/speed-index', // SI 加载性能指标、填充速度// … 
  ],  categories:{ // 须要测量的类别项
  	performance: {…}, accessibility: {…}, 
    best-practices: {…}, seo: {…}, pwa: {…}
	},  groups:{ // 报表功能项标题的聚合及国际化metrics: {…}, 
    seo-mobile: {…}, diagnostics: {…}, 
    pwa-installable: {…},// …
  },  passes: [ // 控制如何加载urlPage,及在加载过程当中收集哪些信息
  	{      passName:'redirectPass', // 惟一标示  blankPage:'about:blank',      // 加载页面时要阻止的请求的URL, * 为放行all  blockedUrlPatterns:['*.css', '*.jpg', '*.jpeg', '*.png', '*.gif', '*.svg', '*.ttf', '*.woff', '*.woff2'],      cpuQuietThresholdMs:0, // Driver 选项,CPU空闲阈值  gatherers: ['http-redirect'],// 收集项  loadFailureMode:'warn', // 加载失败的处理方式,影响后续pass  networkQuietThresholdMs:0,// 距离上个pass完成后安静时长,以确保全部请求瀑布流走完,默认5000  pauseAfterFcpMs:0, // 与 pauseAfterLoadMs 相似  pauseAfterLoadMs:0, // 页面加载后的阻塞的时间,以确保其余的JS脚本已经加载了  recordTrace:false, // 是否启用上个pass跟踪记录  useThrottling:false,// 是否启用限流},
    {      passName:'offlinePass', 
      blockedUrlPatterns: [],      gatherers: ['service-worker'],      loadFailureMode:'ignore'},
    {      passName: 'slowPass',      recordTrace: true, 
      useThrottling: true, 
      networkQuietThresholdMs: 5000, 
      gatherers: ['slow-gatherer'],
    }
  ],  settings:{ // 测量运行过程当中的配置output: 'json',  // 输出格式maxWaitForFcp: 30000,  // 最大等待绘制边界时间maxWaitForLoad: 45000,  // 最大等待加载时间formFactor: 'mobile', // 无线端模式throttling: {…}, // 限流配置// …
  },
  UIStrings (get):() => UIStrings // 国际化相关}复制代码

audits :AuditJSON[],包含了全部审计项

  • 网络层面的是否https、RTT 、服务器延迟/响应、prereload、preconnect
  • 页面加载周期相关的FCP(首次内容绘制)、FMP(首次主内容绘制)、LCP(最后内容绘制)、FCI(首次CPU空闲) 、最大内容元素绘制...
  • 性能状况:预加载脚本/字体、资源汇总、布局位移、长任务、未移除的监听事件...
  • 交互视觉:首次可交互时间、icon、响应式图片、非合成动画、未显示指定size的图片...
  • 可访问性:ARIA(无障碍)、HTML规范、逻辑制表符、ARIA( —— 无障碍)...
  • 解析效率:css/js minified、文本压缩、离屏元素隐藏、是否使用webp、重复脚本、sourcemap...
  • web标准:pwa、long-cache-ttl、manifest、doctype、users-http2...
  • SEO优化:Robots-txt、meta元信息、结构化数据、hreflang...

在输出前每一个 audit 会被注入 lighthouse-core/audits 下的审计逻辑,这些审计逻辑每一个包含 audit(测试分数)、meta(相关信息及计算 Audit 所须要的 Artifact 模块)。

categories :Record<string, CategoryJSON>,也就是日常在DevTool里勾选的几个测试项,包含了要测试了类别。
image.png
groups :Record<string, GroupJSON>,聚合了每一个审计项的 title 及 description,支持后续 UI Report 的国际化。
setting :SharedFlagsSettings,是应用整个测量流程的全局配置,包括网速限制、最大加载时长、report 输出格式、模拟平台、仿真参数、国际化、审计模式、执行通道、请求头等等...
passes :PASSJSON[] ,控制了如何加载 url 请求,以及在加载过程当中收集哪些信息,每一项都是页面的一次 load,好比上面passes.length 表明页面两次加载,默认 pass 提供了 offlinePass 、defaultPass 、redirectPass 针对无网、弱网、脚本实际执行代码量比例的 case,每一个会被注入默认 passConfig 以确保各配置项存在,每一个 pass 都有对应的 gatherers,这些 gatherers 在输出前被注入对应位置下的实例引用,以在 gathering 阶段执行收集逻辑。

// 将 Pass 的 defaultConfig 合并到每一个 passconst passesWithDefaults = Config.augmentPassesWithDefaults(configJSON.passes);// 根据 throttlingMethod 判断是否须要5s来计算指标,默认状况下不须要Config.adjustDefaultPassForThrottling(settings, passesWithDefaults); 
// 注入实例引用const passes = Config.requireGatherers(passesWithDefaults, configDir);复制代码


而后应用 configJSON 拓展配置(目前只有官方默认的lighthouse:default)、合并配置插件与flags插件、校验flags(向下兼容旧版本)、初始化测量运行过程当中的配置,最终产生一个集成gathers收集项、审计项、运行配置项的Runner options.

详细过程过还有对 OnlyAudits/OnlyCategories/skipAudits 配置项的处理,以及对setting、pass、categories的校验每一个audit、categorie 逻辑引用的审查。

ChromeProtocol 交互

const config = generateConfig(configJSON, flags); // 生成Runner Optionsconst options = { url, config }; 
const connection = userConnection || new ChromeProtocol(flags.port, flags.hostname);复制代码

与 Chrome extension App 相似,经过维护的 Chrome Protocol 协议 chrome.debuggger API 链接通讯。

Lighthouse 基于 Websocket 和底层依赖 EventEmit 搭建的 Connection 创建,经过 chrome.debuggger API 与 ChromeLauncher 实例进行通讯。
image.png
与ChromeLauncher的通讯是在实例化Connection的过程当中创建的,但仅仅是创建链接,大部分操做(e.g. 唤起实例是在Lighthouse初始化以前,首次建立tab窗口在实例化Driver以后(connect))。新建RequestUrl tab窗口后经过 ChromeLauncher 返回的 webSocketDebuggerUrl 建立 webSocket 链接,调用域能力,派发给 Driver 收集 Gatherers。

浏览器API Protocol:chromedevtools.github.io/devtools-pr…
域能力API Protocol:chromedevtools.github.io/devtools-pr…
域能力API MAP:github.com/ChromeDevTo…
Driver Event Map: github.com/ChromeDevTo…

收集Gatherer

requestUrl 仅支持如下几种协议类型的 href

const allowedProtocols = ['https:', 'http:', 'chrome:', 'chrome-extension:'];复制代码

校验经过后,执行 gatherFn ,开始加载页面,尝试收集全部 passes 聚合的 Artifacts。但在收集过程当中,还须要作初始化环境及收集 gatherers,主要逻辑在 GatherRunner.run 内执行。

  static async _gatherArtifactsFromBrowser(requestedUrl, runnerOpts, connection) {if (!runnerOpts.config.passes) {      throw new Error('No browser artifacts are either provided or requested.');
    }const driver = runnerOpts.driverMock || new Driver(connection);const gatherOpts = {
      driver,
      requestedUrl,      settings: runnerOpts.config.settings,
    };const artifacts = await GatherRunner.run(runnerOpts.config.passes, gatherOpts);return artifacts;
  }复制代码

Driver 做为 Connection 的驱动程序,控制 Connection 以 Chrome.debugger API 规范调用域能力。

async run(passConfigs, options) {const driver = options.driver;const artifacts = {};try {      // 建立新tab,与返回的 webSocketDebuggerUrl 创建 socket 链接  await driver.connect(); 
      
    	// 加载about:blank 空白页,执行一次仿真逻辑  await GatherRunner.loadBlank(driver); 
      
      // 初始化 Artifacts 结构以便后续数据填充  const baseArtifacts = await GatherRunner.initializeBaseArtifacts(options);      
      // 计算CPU基准性能? https://docs.google.com/spreadsheets/d/1E0gZwKsxegudkjJl8Fki_sOwHKpqgXwt8aBAfuUaB8A/edit#gid=0  baseArtifacts.BenchmarkIndex = await options.driver.getBenchmarkIndex();			
      // 设定 Driver 偏好  await GatherRunner.setupDriver(driver, options, baseArtifacts.LighthouseRunWarnings);      // ...跑pass} catch (err) {
      GatherRunner.disposeDriver(driver, options);      throw err;
    }
  }复制代码

须要尽量纯净的环境,摒弃Chrome程序自己带来的影响,为了防止其余服务/程序与Driver共享目标URL Tab,初次会自动导航到 about:blank,进行一次仿真模拟流程以初始化空白的上下文。在跑 pass 以前设定 Driver 偏好,setupDriver 主要作了如下几件事:

  • 检查是否有做用域当前origin的ServiceWork,屏蔽干扰。
  • 设置 UA 和仿真参数。
  • 启用 Runtime 上下文,为现有上下文当即执行事件。
  • 跳过 DebuggerPause 而且设异步Request跟踪深度处理过分嵌套的回调
  • 缓存原生对象 (Promise,Performance,Error,URL,ElementMatches) 以防止被外部引入的 polyfill 破坏。
  • 启用 PerformanceObserver,开始监听 longTask 及 CPU 空闲情况
  • 静默对话框 (alert/confirm/prompt) 保证流程通畅。
  • 利用 requestIdleCallback 进行CPU降速,也就是 Performance 面板的 CPU slowdown。


完成准备工做后,开始跑pass用例。不指定passes状况下默认为 offlinePass、defaultPass、redirectPass。

let isFirstPass = true;for (const passConfig of passConfigs) {  const passContext = {
    driver,url: options.requestedUrl,settings: options.settings,
    passConfig,
    baseArtifacts,LighthouseRunWarnings: baseArtifacts.LighthouseRunWarnings,
  };  // 从about:blank开始加载目标页面并从 pass 中执行 gatherers 以收集 artifacts
  const pa***esults = await GatherRunner.runPass(passContext);  Object.assign(artifacts, pa***esults.artifacts);  // 遇到页面加载错误直接退出
  if (pa***esults.pageLoadError && passConfig.loadFailureMode === 'fatal') {
    baseArtifacts.PageLoadError = pa***esults.pageLoadError;break;
  }  if (isFirstPass) {// 填充 manifest 相关信息await GatherRunner.populateBaseArtifacts(passContext);
    isFirstPass = false;
  }	// 禁用请求拦截器
  await driver.fetcher.disableRequestInterception();
}复制代码

每次runPass都是一次完整的加载页面

async runPass(passContext) {  const gathererResults = {};  const {driver, passConfig} = passContext;  await GatherRunner.loadBlank(driver, passConfig.blankPage);  await GatherRunner.setupPassNetwork(passContext);  if (GatherRunner.shouldClearCaches(passContext)) {await driver.cleanBrowserCaches(); // Clear disk & memory cache if it's a perf run
  }  await GatherRunner.beforePass(passContext, gathererResults);  await GatherRunner.beginRecording(passContext);  const {navigationError: possibleNavError} = await GatherRunner.loadPage(driver, passContext);  await GatherRunner.pass(passContext, gathererResults);  const loadData = await GatherRunner.endRecording(passContext);  await driver.setThrottling(passContext.settings, {useThrottling: false});
  GatherRunner._addLoadDataToBaseArtifacts(passContext, loadData, passConfig.passName);  await GatherRunner.afterPass(passContext, loadData, gathererResults);  const artifacts = GatherRunner.collectArtifacts(gathererResults);  return artifacts
}复制代码

分为如下几个步骤

  • 先将页面导航到 about:blank。
  • 根据 pass 预配置网络环境。
  • 按需清除硬盘、内存中的缓存。
  • 执行 beforePass,过程当中遍历当前 pass 的 Gatherers,执行每一个 gatherer 实例的 beforePass Hook,拿到结果存到 gathererResults 供 pass 使用。
  • 记录 DevToolLog 和 Trace,后续 Auditing 分析可能用到。
  • 将页面导航到目标URL,处理重定向等待完整加载后更新 Navigation 信息。
  • 执行 pass Hook,执行时还未收集到相关 Log 及 Trace。
  • 中止 DevToolLog 监听,输出 DevToolLogs、NetworkLogs、TraceLogs。
  • 禁用网络节流,为 afterPass 分析提供准备。
  • 判断是否存在页面加载错误,若是存在,则不返回 Artifacts ,终止后续步骤。
  • 保存 DevtoolLogs 和 Trace 记录到 Artifacts。
  • 执行 afterPass Hook,遍历当前 pass 中每一个 gatherer 实例并提供 DevtoolLogs 与 Trace 给 afterPass Hook。
  • 收集 gathererResult 每一个 gatherer afterPass 结果。输出 Artifacts。
class Gatherer {  get name() { return this.constructor.name;}  // 导航前调用
  beforePass(passContext) { }  // 页面加载后调用
  pass(passContext) { }  // gatherers 全部 pass 都执行完毕后执行。
  afterPass(passContext, loadData) { }
}复制代码

每一个 gatherer 包含三个Hook,Artifact 取最后一次Hook输出的结果,e.g.当afterPass未吐出,则采用 pass 结果,以此类推。在每一个 Hook 内控制 Driver 调用域能力获取采集结果,最终输出 Artifacts。
例如 CSSUsage

class CSSUsage extends Gatherer {  async afterPass(passContext) {const driver = passContext.driver;const stylesheets = [];// ...// 获取styleSheetconst promises = stylesheets.map(sheet => {      const styleSheetId = sheet.header.styleSheetId;      return driver.sendCommand('CSS.getStyleSheetText', {styleSheetId}).then(content => {return {          header: sheet.header,          content: content.text,
        };
      });
    });const styleSheetInfo = await Promise.all(promises);// 获取CSS使用率const ruleUsageResponse = await driver.sendCommand('CSS.stopRuleUsageTracking');const dedupedStylesheets = new Map(styleSheetInfo.map(sheet => {      return [sheet.content, sheet];
    }));return {      rules: ruleUsageResponse.ruleUsage,      stylesheets: Array.from(dedupedStylesheets.values()),
    };
  }
}复制代码

收集完 Artifacts 后 Driver 完成了它的使命,被 disconnect。 baseArtifacts 也完成定稿,Gathering 阶段结束,开始执行审计逻辑。

执行审计

审计的流程依赖于 Artifacts 收集的信息聚合,每一个审计由 lighthouse-core/audits 下的内置 Audit 和 configPath 指定的组成,经过传递 Artifacts 给 Audit.audit 审计函数,audit 拿到本身想要的数据进行逻辑运算,返回该审计函数对结果评估的分数和一系列详情数据。该分数大部分状况下处于(0-1)之间,分值的范围取决于对应 Audit id 设置的权重。

audit 的数量远胜 gatherers,分开管理的缘由是为了方便管理和拓展额外指标与audit,将二者责任与分工梳理清除。

const auditResultsById = await Runner._runAudits(settings, runOpts.config.audits, artifacts, lighthouseRunWarnings);复制代码

每一个 audit 的主要结构以下

class Audit {  // 计分方式
  static get SCORING_MODES() {}  // 审计组件元信息 包含id标识、标题、失败标题、描述、审计所需Artifact模块、分数展现模式
  static get meta() {}  // 审计主逻辑
  static audit(artifacts, context) {}  // 给定分数根据对数正态分布生成分数
  static computeLogNormalScore(controlPoints, value) {}  
  // 生成表形式的详情和总览
  static makeTableDetails(headings, results, summary) {}  // 生成列表形式的详情
  static makeListDetails(items) {}  // 生成片断详情static makeSnippetDetails() {}  // 生成可能的优化点列表信息
  static makeOpportunityDetails(headings, items, overallSavingsMs, overallSavingsBytes) {}  // 生成错误结果
  static generateErrorAuditResult(audit, errorMessage) {}  // 生成Audit结果
  static generateAuditResult(audit, product) {}
}// audit/longTasks.jsclass LongTasks extends Audit {  static get meta() {return {      id: 'long-tasks',      scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,      title: str_(UIStrings.title),      description: str_(UIStrings.description),      requiredArtifacts: ['traces', 'devtoolsLogs'],
    };
  }  static async audit(artifacts, context) {// ...const longtasks = tasks
      .map(t => {const timing = taskTimingsByEvent.get(t.event) || DEFAULT_TIMING;return { ...t, duration: timing.duration, startTime: timing.startTime };
      })
      .filter(t => t.duration >= 50 && !t.unbounded && !t.parent)
      .sort((a, b) => b.duration - a.duration)
      .slice(0, 20);return {      score: results.length === 0 ? 1 : 0,      notApplicable: results.length === 0,      details: tableDetails,
      displayValue,
    };
  }
}复制代码

审计过程:

  • 每一个 Audit 导入所依赖的 Artifact 模块并检查是不是有效的模块。
  • 收集好 Artifact 依赖传递给 Audit.audit 执行审计主逻辑。
  • 将审计结果再传递给 generateAuditResult 返回 LHAR 对象。

创建表将 LHAR 收集起来,供给 Categories 统计分值使用。

async _runAudits(settings, audits, artifacts, runWarnings) {  const auditResultsById = {}; // auditResult聚合
  for (const auditDefn of audits) {const auditId = auditDefn.implementation.meta.id;const auditResult = await Runner._runAudit(auditDefn, artifacts, sharedAuditContext, runWarnings);
    auditResultsById[auditId] = auditResult;
  }  return auditResultsById;
}async _runAudit(auditDefn, artifacts, sharedAuditContext, runWarnings) {  const audit = auditDefn.implementation;  for (const artifactName of audit.meta.requiredArtifacts) {// ... 校验依赖
  }  const auditOptions = Object.assign({}, audit.defaultOptions, auditDefn.options);  const auditContext = {options: auditOptions,
    ...sharedAuditContext,
  };  // 引入依赖
  const requestedArtifacts = audit.meta.requiredArtifacts.concat(audit.meta.__internalOptionalArtifacts || []);  const narrowedArtifacts = requestedArtifacts.reduce((narrowedArtifacts, artifactName) => {const requestedArtifact = artifacts[artifactName];
    narrowedArtifacts[artifactName] = requestedArtifact;return narrowedArtifacts;
  }, {});  // 执行审计主流程
  const product = await audit.audit(narrowedArtifacts, auditContext);
  runWarnings.push(...product.runWarnings || []);  // 生成LHR对象
  auditResult = Audit.generateAuditResult(audit, product);	return auditResult;
}复制代码

JSON & Output

LHAR score 仍属于对数正态分布生成的还未与通过映射运算,不算做最终展现的分值,分值是根据设置的 Categories 统计对应 Category 的 (weight(权重)*score(分数))/weight sum(权重总和)。权重声明在默认 config文件,也能够经过外部导入或者命令行参数 --config-path 指定配置文件来改变,分值则依赖于 Audit 审计返回的 AuditResult 聚合,取对应 Category id 标识 score,须要注意的是只有明确展现的 Categoies 才具有分值项。
image.png
以后则是国际化与依赖 ReportRender 输出JSON/HTML/CSV报告,至此流程over。回过头再看总体流程图清晰许多。
image.png

对 Driver 的学习可以梳理 DevTool 和 Chrome 之间的关系和认知,对 gatherers 和 audit 的学习可以让咱们认清前端性能的最新标准,很是值得深挖。

自绘流程

image.png

文件依赖

image.png