起因
前段时间紧急上线了一个门户项目,两端静态页面,首页考虑到需要极致体验必须使用硬编码搭建,部分子页面采用可视化搭建,要求Lighthouse必须接近满分,尽管通过一些手段优化了首屏但上线之后,离目标还有一大段偏差。

于是去挖lh源码关注各类指标对分值的影响程度,有了针对性的方向,剩下的工作就简单的多。

顺便整理了源码。
LightHouse流程架构
Lighthouse 是一个开源的自动化工具,提供了Node、Chrome Extension App、Chrome DevTool 三端,通过输入审查网址及配置项,通过一系列模拟测试特定环境下的运行状况和性能分析,最后生成性能结果页面供可视化浏览。
为什么需要Lighthouse?一直以来,前端性能的分析指标过于泛化,得不到有效统一的标准,特别是近几年SPA、微服务、小程序、Flutter、webAssembly、SSR、ServerLess等前端技术方案百花齐放,得到高速发展的同时,一些传统的性能测量指标和方式落后跟不上脚步,无法支撑现有技术体系和新领域的迭代更新,再加上终端环境复杂、用户体验标准难以衡量、兼容性问题,审计指标越来越复杂。
例如阿里云ARMS针对 SPA 应用的FMP计量方式改成了依赖于MutationObserve计算权重变化最大的时间节点;淘宝前端团队的秒开率标准;岳鹰结合jssdk与Android内核查看汇集绘制指令来判断页面是否处于白屏….都表明在大前端趋势不可逆转,而测量性能的方式需要考虑更多环境和因素,变得愈加复杂。
Lighthouse 一定不是大前端下性能统计标准,因为从目前而言仍只适用于web端,并且其统计的指标过于笼统。本身而言依赖于 DevTool 发送回来的综合报告按 Audit 分析,输出对应的抽象分数、核心点和优化项,分数低不一样代表性能差,但分数高一定是性能上佳。
整体流程

名词释义
Driver
根据 Chrome Debugging Protocol <URL>与浏览器交互的对象
Gatherers
驱动 Driver 收集到的网页基础信息,用于后续 Auditing 的审计逻辑。
Artifacts
一系列 Gatherers 信息集合。在 Auditing 里会被附加其他信息,被多个Audits共享。
Audits
以指定依赖的 Artifacts 作为输入,测试单个功能/优化/指标,审计测试评估分数,得到一组LHAR(LightHouse Audit Result Object) 标准数据对象。
Report
ReportRender 使用LHR结果创建输出的UI报表。
基本概念
Lighthouse 驱动 Driver 通过 Chrome DevTool Protocol 与浏览器交互,执行一系列命令,先生成 Gatherers 模块用以收集 Artifacts 信息,这些 Artifacts 信息的聚合会在 Auditing 阶段作为 Audit case 逻辑的输入凭证,通过定义的一系列自定义的审计标准输出分数/优化/详情/描述/原因/展示形式/错误等信息,最终得到一系列LHR统计结果,按需生成指定文件。
基本常用的命令如下,具体命令就不贴了
文档传送门
$ lighthouse --help
lighthouse <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如下,

runLighthouse
负责唤起 ChromeLauncher 和调用 lighthouse 。
let launchedChrome;
try { const shouldGather = flags.gatherMode || flags.gatherMode === flags.auditMode; if (shouldGather) { launchedChrome = await getDebuggableChrome(flags); flags.port = launchedChrome.port; } const runnerResult = await lighthouse(url, flags, config);
if (runnerResult) { await saveResults(runnerResult, flags); }
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) { flags.logLevel = flags.logLevel || 'error'; log.setLevel(flags.logLevel);
const config = generateConfig(configJSON, flags); const options = { url, config }; const connection = userConnection || new ChromeProtocol(flags.port, flags.hostname);
const gatherFn = ({requestedUrl}) => { return Runner._gatherArtifactsFromBrowser(requestedUrl, options, connection); }; return Runner.run(gatherFn, options); }
|
生成Runner Options
假设没传入 configJSON 文件,将默认使用 default-config.js
。setting
const defaultConfig = { setting, audits: [ 'is-on-https', 'service-worker', 'metrics/first-contentful-paint', 'metrics/largest-contentful-paint', 'metrics/first-meaningful-paint', 'metrics/speed-index', ], categories:{ performance: {…}, accessibility: {…}, best-practices: {…}, seo: {…}, pwa: {…} }, groups:{ metrics: {…}, seo-mobile: {…}, diagnostics: {…}, pwa-installable: {…}, }, passes: [ { passName:'redirectPass', blankPage:'about:blank', blockedUrlPatterns:['*.css', '*.jpg', '*.jpeg', '*.png', '*.gif', '*.svg', '*.ttf', '*.woff', '*.woff2'], cpuQuietThresholdMs:0, gatherers: ['http-redirect'], loadFailureMode:'warn', networkQuietThresholdMs:0, pauseAfterFcpMs:0, pauseAfterLoadMs:0, recordTrace:false, 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里勾选的几个测试项,包含了要测试了类别。

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 阶段执行收集逻辑。
const passesWithDefaults = Config.augmentPassesWithDefaults(configJSON.passes);
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); const 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 实例进行通信。

与ChromeLauncher的通信是在实例化Connection的过程中建立的,但仅仅是建立连接,大部分操作(e.g. 唤起实例是在Lighthouse初始化之前,首次创建tab窗口在实例化Driver之后(connect))。新建RequestUrl tab窗口后通过 ChromeLauncher 返回的 webSocketDebuggerUrl 创建 webSocket 连接,调用域能力,派发给 Driver 收集 Gatherers。
浏览器API Protocol:https://chromedevtools.github.io/devtools-protocol/
域能力API Protocol:https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker/
域能力API MAP:https://github.com/ChromeDevTools/devtools-protocol/blob/master/types/protocol-mapping.d.ts#L625
Driver Event Map: https://github.com/ChromeDevTools/devtools-protocol/blob/master/types/protocol-mapping.d.ts#L11
收集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 { await driver.connect(); await GatherRunner.loadBlank(driver); const baseArtifacts = await GatherRunner.initializeBaseArtifacts(options); baseArtifacts.BenchmarkIndex = await options.driver.getBenchmarkIndex(); await GatherRunner.setupDriver(driver, options, baseArtifacts.LighthouseRunWarnings);
} 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, }; const passResults = await GatherRunner.runPass(passContext); Object.assign(artifacts, passResults.artifacts);
if (passResults.pageLoadError && passConfig.loadFailureMode === 'fatal') { baseArtifacts.PageLoadError = passResults.pageLoadError; break; }
if (isFirstPass) { 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(); } 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) { } afterPass(passContext, loadData) { } }
|
每个 gatherer 包含三个Hook,Artifact 取最后一次Hook输出的结果,e.g.当afterPass未吐出,则采用 pass 结果,以此类推。在每个 Hook 内控制 Driver 调用域能力获取采集结果,最终输出 Artifacts。
以 css-usage 为例
class CSSUsage extends Gatherer { async afterPass(passContext) { const driver = passContext.driver; const stylesheets = []; const onStylesheetAdded = sheet => stylesheets.push(sheet); driver.on('CSS.styleSheetAdded', onStylesheetAdded); await driver.sendCommand('DOM.enable'); await driver.sendCommand('CSS.enable'); await driver.sendCommand('CSS.startRuleUsageTracking'); await driver.evaluateAsync('getComputedStyle(document.body)'); driver.off('CSS.styleSheetAdded', onStylesheetAdded); const 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); 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() {} 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) {} static generateAuditResult(audit, product) {} }
|
审计过程:
- 每个 Audit 导入所依赖的 Artifact 模块并检查是否是有效的模块。
- 收集好 Artifact 依赖传递给 Audit.audit 执行审计主逻辑。
- 将审计结果再传递给 generateAuditResult 返回 LHAR 对象。
以 longTask 为例
class 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 settings = context.settings || {}; const trace = artifacts.traces[Audit.DEFAULT_PASS]; const tasks = await MainThreadTasks.request(trace, context); const devtoolsLog = artifacts.devtoolsLogs[LongTasks.DEFAULT_PASS]; const networkRecords = await NetworkRecords.request(devtoolsLog, context);
const taskTimingsByEvent = new Map(); if (settings.throttlingMethod === 'simulate') { const simulatorOptions = {trace, devtoolsLog, settings: context.settings}; const pageGraph = await PageDependencyGraph.request({trace, devtoolsLog}, context); const simulator = await LoadSimulator.request(simulatorOptions, context); const simulation = await simulator.simulate(pageGraph, {label: 'long-tasks-diagnostic'}); for (const [node, timing] of simulation.nodeTimings.entries()) { if (node.type !== 'cpu') continue; taskTimingsByEvent.set(node.event, timing); } } else { for (const task of tasks) { if (task.unbounded || task.parent) continue; taskTimingsByEvent.set(task.event, task); } } const jsURLs = BootupTime.getJavaScriptURLs(networkRecords); 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);
const results = longtasks.map(task => ({ url: BootupTime.getAttributableURLForTask(task, jsURLs), duration: task.duration, startTime: task.startTime, }));
const headings = [ {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, {key: 'startTime', itemType: 'ms', granularity: 1, text: str_(i18n.UIStrings.columnStartTime)}, {key: 'duration', itemType: 'ms', granularity: 1, text: str_(i18n.UIStrings.columnDuration)}, ]; const tableDetails = Audit.makeTableDetails(headings, results);
let displayValue; if (results.length > 0) { displayValue = str_(UIStrings.displayValue, {itemCount: results.length}); }
return { score: results.length === 0 ? 1 : 0, notApplicable: results.length === 0, details: tableDetails, displayValue, }; } }
|
建立表将 LHAR 收集起来,供给 Categories 统计分值使用。
async _runAudits(settings, audits, artifacts, runWarnings) { const auditResultsById = {}; 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 || []); 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 才具备分值项。

之后则是国际化与依赖 ReportRender 输出JSON/HTML/CSV报告,至此流程over。现在再看整体流程图,清晰许多。

自绘流程

对Driver的学习能够梳理 DevTool 和 Chrome 之间的关系和认知,对 gatherers 和 audit 的学习能够让我们认清前端性能的最新标准,非常值得深挖。
文件依赖
