Lighthouse 插件开发实战:用 lighthouse-plugin-example 模板打造自定义审计
【免费下载链接】lighthouseAutomated auditing, performance metrics, and best practices for the web.项目地址: https://gitcode.com/GitHub_Trending/lig/lighthouse
本指南基于 Lighthouse 官方提供的 lighthouse-plugin-example 插件模板,完整讲解从零搭建一个 Lighthouse 插件的全过程:包括三个核心文件的职责拆解、本地开发与迭代运行方式、面向插件使用者的安装与执行方法,并结合仓库源码深入剖析插件配置的解析校验机制与自定义审计的编写规范。读完本文,你将具备独立开发、调试并发布一个可共享的 Lighthouse 插件(在报告中新增自定义审计类别)的完整实战能力。
Lighthouse 插件是什么
Lighthouse 插件(Plugin)是一种扩展 Lighthouse 能力的方式:它本质上是一个 Node 模块,实现一组由 Lighthouse 执行、并以新增类别形式出现在报告中的检查项。插件非常适合领域专家(例如 SEO、广告、可访问性团队)把特定领域的检查逻辑沉淀成可复用的模块,再通过 NPM 分享给其他 Lighthouse 用户。
在动手之前,需要先分清插件与自定义配置(Custom Config)的边界。插件易于共享、API 在 minor 版本之间保持稳定,但作用范围也比自定义配置更受限。官方在 plugins.md 中给出了能力对照:
| 能力 | 插件 | 自定义配置 |
|---|---|---|
| 引入自定义审计 | ✅ | ✅ |
| 新增自定义类别 | ✅ | ✅ |
| 易于在 NPM 上共享与扩展 | ✅ | ❌ |
| Semver 稳定的 API | ✅ | ❌ |
| 从页面采集自定义数据(artifacts) | ❌ | ✅ |
| 修改核心类别 | ❌ | ✅ |
修改config.settings属性 | ❌ | ✅ |
可见,如果只是想在报告中加自己的审计和类别,插件是首选;而需要采集页面自定义数据或修改核心配置时,应转向自定义 Lighthouse 配置。
模板三件套:从文件结构读懂插件形态
Recipe 目录(docs/recipes/lighthouse-plugin-example)一共包含三个关键文件,恰好构成一个最小可用插件的全部骨架:
package.json—— 声明插件的入口(plugin.js)与依赖关系;plugin.js—— 指示 Lighthouse 运行插件自带的preload-as.js审计,并描述报告中的新类别及其详情;audits/preload-as.js—— 在 Lighthouse 默认审计之外新增的审计逻辑。
下面逐一拆解这三个文件。
package.json:一个「名正言顺」的 NPM 模块
插件的package.json(package.json)写法如下:
{ "name": "lighthouse-plugin-example", "private": true, "type": "module", "main": "./plugin.js", "peerDependencies": { "lighthouse": "^13.4.1" }, "devDependencies": { "lighthouse": "^8.6.0" } }要点有两处:其一,Lighthouse 通过插件名识别插件,模块名必须以lighthouse-plugin-开头;其二,插件不应直接依赖 Lighthouse 作为运行时依赖,而是用peerDependencies向使用方声明所需的 Lighthouse 版本,用devDependencies供本地开发使用(当前仓库模板里peerDependencies为^13.4.1,devDependencies为^8.6.0,实际开发时建议两者对齐)。"main"指向的plugin.js就是插件配置的入口文件。
plugin.js:声明审计、类别与评分
plugin.js(plugin.js)是插件的配置核心,它声明了要新增的审计、类别名称以及评分方式:
/** @type {LH.Config.Plugin} */ export default { // Additional audit to run on information Lighthouse gathered. audits: [{ path: 'lighthouse-plugin-example/audits/preload-as.js', }], // A new category in the report for the new audit's output. category: { title: 'My Plugin Category', description: 'Results for our new plugin category.', auditRefs: [ {id: 'preload-as', weight: 1}, {id: 'meta-description', weight: 1}, // Can also reference default Lighthouse audits. ], }, };这个文件包含两个必需属性:
audits:新审计的路径数组。每个路径应写成像使用者传给模块解析器那样的「绝对」形式,即lighthouse-plugin-<你的插件名>/path/to/audits/audit-file.js(对应类型为Array<{path: string}>)。category:新类别的展示信息与评分配置,至少需要title和auditRefs两个属性。auditRefs中每一项是{id, weight, group?},weight决定该审计在整个类别得分中的权重。
注意auditRefs里除了插件自己的preload-as,还可以直接引用 Lighthouse 的默认审计(如这里的meta-description),这正是插件「复用核心审计能力」的体现。
audits/preload-as.js:自定义审计的实现样例
audits/preload-as.js(audits/preload-as.js)是一个完整的自定义审计实现,检查页面里所有<link rel=preload>标签是否带有正确的as属性:
import {Audit} from 'lighthouse'; // https://fetch.spec.whatwg.org/#concept-request-destination const allowedTypes = new Set(['font', 'image', 'script', 'serviceworker', 'style', 'worker']); class PreloadAsAudit extends Audit { static get meta() { return { id: 'preload-as', title: 'Preloaded requests have proper `as` attributes', failureTitle: 'Some preloaded requests do not have proper `as` attributes', description: '`<link rel=preload>` tags need an `as` attribute to specify the type of ' + 'content being loaded.', requiredArtifacts: ['LinkElements'], }; } static audit(artifacts) { const preloadLinks = artifacts.LinkElements.filter(el => el.rel === 'preload'); const noAsLinks = preloadLinks.filter(el => !allowedTypes.has(el.as)); const passed = noAsLinks.length === 0; return { score: passed ? 1 : 0, displayValue: `Found ${noAsLinks.length} preload requests with missing \`as\` attributes`, }; } } export default PreloadAsAudit;一个审计类需要实现两个静态成员:
meta:审计的元信息,包括id(kebab-case 字符串标识,通常与文件名一致)、成功与失败时的标题(title/failureTitle)、说明审计重要性的description(支持 Markdown 链接),以及requiredArtifacts(本审计运行时必须存在的 artifacts 列表)。这里声明了LinkElements,即页面链接元素的采集产物。audit(artifacts):审计主逻辑,接收artifacts(键为requiredArtifacts中声明的产物),返回score(0 到 1)以及可选的displayValue等展示信息。
该审计从artifacts.LinkElements中筛出rel === 'preload'的链接,再检查as属性是否落在 Fetch 规范允许的目标类型集合['font', 'image', 'script', 'serviceworker', 'style', 'worker']内,只要有一个缺失或非法即判失败。
从源码看LinkElements的来历:该 artifact 由 core/gather/gatherers/link-elements.js 采集,其getArtifact方法同时合并了来自 DOM 与 HTTP 响应头两路数据,并统一将rel小写化,方便下游审计消费(见getLinkElementsInDOM与getLinkElementsInHeaders两条采集路径)。这也解释了为什么审计里可以直接用el.rel === 'preload'做过滤。
插件开发者视角:从模板起步
以 Recipe 为模板初始化项目
把本仓库的 recipe 目录拷到自己的新项目里即可起步:
mkdir lighthouse-plugin-example && cd lighthouse-plugin-example curl -L https://github.com/GoogleChrome/lighthouse/archive/main.zip | tar -xzv mv lighthouse-main/docs/recipes/lighthouse-plugin-example/* ./ rm -rf lighthouse-main注意:上面的命令是从 GitHub 拉取 Lighthouse 主分支压缩包再解压提取;在当前仓库内,你可以直接以 docs/recipes/lighthouse-plugin-example 目录为模板复制。重命名插件时,务必同步重命名其目录名(插件名、目录名、
audits中的模块路径三者需保持一致)。
只运行自己的插件
安装依赖并单独运行插件:
yarn NODE_PATH=.. npx lighthouse -- https://example.com --plugins=lighthouse-plugin-example --only-categories=lighthouse-plugin-example --view这里NODE_PATH=..是一个本地开发技巧:把父目录加入 Node 模块解析路径,让 Lighthouse 能解析到尚未发布的插件(当插件从 NPM 安装为 node module 时不需要)。关键 CLI 参数如下(对应实现见 cli/cli-flags.js):
--plugins:运行指定插件,支持逗号分隔多个值(array: true+splitCommaSeparatedValues);--only-categories:只运行指定类别(内置类别如accessibility、best-practices、performance、seo等,这里传入插件的类别名lighthouse-plugin-example);--view:运行结束后在浏览器中打开 HTML 报告。
迭代开发:采集一次、反复审计
为避免每次改动审计逻辑都要重新打开浏览器采集,可以拆成两阶段:
# 阶段一:从浏览器采集 artifacts 并保存到磁盘 NODE_PATH=.. npx lighthouse -- https://example.com --plugins=lighthouse-plugin-example --only-categories=lighthouse-plugin-example --gather-mode # 阶段二:反复重跑,只做审计(可加 --view 预览) NODE_PATH=.. npx lighthouse -- https://example.com --plugins=lighthouse-plugin-example --only-categories=lighthouse-plugin-example --audit-mode --view对应 cli/cli-flags.js 中的定义:--gather-mode(别名-G)采集 artifacts 并保存到磁盘,若未同时开启 audit-mode 则提前退出;--audit-mode(别名-A)从磁盘读取已保存的 artifacts 进行处理,未指定目录时默认取./latest-run/。这一「采集/审计分离」的工作流能显著缩短插件迭代的反馈周期。
发布到 NPM
迭代完成后,npm publish(或yarn publish)将插件发布到 NPM,供他人以普通依赖方式安装使用。
插件使用者视角:安装与运行
作为插件用户(而非开发者),只需三步:
- 安装
lighthouse(v5+)与插件lighthouse-plugin-example,通常作为devDependencies:npm install -D lighthouse lighthouse-plugin-example - 运行本地 Lighthouse 二进制,有三种方式任选其一:
npx --no-install lighthouse -- https://example.com --plugins=lighthouse-plugin-example --viewyarn lighthouse https://example.com --plugins=lighthouse-plugin-example --view- 在
package.json中新增一个调用lighthouse的 npm script 再执行。
- 打开生成的 HTML 报告,查看插件新增的「My Plugin Category」类别及其审计结果。
深入源码:插件配置如何被解析与校验
插件plugin.js里的对象并不是直接塞给核心运行的,而是经过 core/config/config-plugin.js 中ConfigPlugin.parsePlugin(pluginJson, pluginName)的严格解析与校验。理解这段源码有助于写出「一次通过」的插件配置:
- 入参防御:先
JSON.parse(JSON.stringify(...))深拷贝并去激活 live 属性,再断言插件整体是一个普通对象,多余的顶层键会抛出${pluginName} has unrecognized properties: [...]错误; - audits 解析(
_parseAuditsList):允许缺省(不新增审计时回退undefined),但若提供则必须是对象数组,且每个对象只能有path一个键,path必须是字符串; - category 解析(
_parseCategory):title必填且须为字符串或 ICU 消息;description、manualDescription可选;auditRefs必填且每项{id, weight, group?}都要通过类型校验,其中group会被自动加上${pluginName}-前缀以命名空间隔离; - groups 解析(
_parseGroups):可选,键为 group ID、值为{title, description?},同样会统一加上插件名前缀,避免与核心类别分组冲突; - supportedModes:可选项,必须是
'navigation'、'timespan'、'snapshot'三种模式组成的数组,缺省时表示类别支持全部模式。
最终parsePlugin返回的配置结构为{audits, categories: {[pluginName]: {...}}, groups},即把插件类别挂载到以插件名为键的类别表中,与核心类别并存。
自定义审计的编写指南
meta 字段约定
meta是审计的元数据,需覆盖以下字段:
id: string(必填):kebab-case 审计标识,通常与文件名一致;title: string(必填):审计通过时的简短可见标题;failureTitle: string(可选):审计失败时的简短标题;description: string(必填):说明审计重要性的详细描述,支持 Markdown 链接;requiredArtifacts: Array<string>(必填):审计执行所必需的 artifacts 列表;scoreDisplayMode: "numeric" | "binary" | "manual" | "informative"(可选):分数展示方式的标识。
audit(artifacts, context) 与可用 artifacts
audit()接收两个参数:artifacts的键即requiredArtifacts中声明的产物;context是内部对象,主要用途是从DevtoolsLog派生网络请求信息。审计的核心目标是基于artifacts返回 0~1 的score。
插件可稳定使用的 artifacts 包括:fetchTime、BenchmarkIndex、settings、Timing、HostFormFactor、HostUserAgent、HostProduct、GatherContext、URL、ConsoleMessages、DevtoolsLog、MainDocumentContent、ImageElements、LinkElements、MetaElements、Scripts、Trace、ViewportDimensions。列表之外的 artifacts 被视为实验性产物,结构随时可能变化,应谨慎使用。
处理网络请求
网络请求的原始信息藏在DevtoolsLogartifact 中(页面加载期间的全部 DevTools Protocol 流量),审计时再派生请求对象:
import {Audit, NetworkRecords} from 'lighthouse'; class HeaderPoliceAudit { static get meta() { return { id: 'header-police-audit-id', title: 'All headers stripped of debug data', failureTitle: 'Headers contained debug data', description: 'Pages should mask debug data in production.', requiredArtifacts: ['DevtoolsLog'], }; } static async audit(artifacts, context) { const devtoolsLog = artifacts.DevtoolsLog; // 传入 context 以便 Lighthouse 缓存派生结果,避免每个审计重复计算。 const requests = await NetworkRecords.request(devtoolsLog, context); const badRequests = requests.filter(request => request.responseHeaders.some(header => header.name.toLowerCase() === 'x-debug-data') ); return { score: badRequests.length === 0 ? 1 : 0, }; } } export default HeaderPoliceAudit;最佳实践与常见误区
命名规范
- 类别标题:短小(少于 20 字符),理想情况下是单个词或缩写,避免加「Lighthouse」「Plugin」这类冗余前缀;
- 类别描述:为插件审计提供上下文,并链接到用户可进一步了解或提问的地方;
- 审计标题:用现在时描述页面「做对了 / 做错了什么」。例如「Document has a
<title>element」而非「Good job onaltattributes」; - 审计描述:简要说明审计为什么重要,并用 Markdown 链接指向更详细的指南。
评分要点
- 按重要性为每个审计分配
weight; - 在单个审计内部用 0~1 之间的数值区分得分(默认得分 > 0.9 会收进「Passed Audits」折叠区);
- 当审计不适用时,返回
{score: null, notApplicable: true}而不是硬给分。
常见错误
- 忘记过滤:页面会存在各种边界情况——非网络请求(
blob:、data:、file:)、非 JavaScript 脚本(type="x-shader/x-vertex"等)、跟踪像素图(1x1、0x0 尺寸的图片)等,审计前务必考虑这些情况; - 忘记归一化:artifacts 通常如实反映页面观测值,只按规范做最小归一化,因此响应头名称/值、脚本
type、脚本src等字段可能带空白、大小写混用、缺失或相对 URL,处理时要自行归一化。
运行结果
模板中附带的截图 plugin-recipe-screenshot.png 展示了插件跑通后的报告效果:新增的「My Plugin Category」类别出现在报告中(示例得分为 50),其中「Preloaded requests have properasattributes」审计通过(提示Found 0 preload requests with missing as attributes),同时复用的默认审计「Document does not have a meta description」作为失败项展示,红色/绿色状态与折叠面板的呈现方式和 Lighthouse 内置类别完全一致。
如果你希望进一步深入了解插件机制与审计设计,推荐继续阅读仓库内的 插件手册、配置说明、新审计编写指南 以及 整体架构。
【免费下载链接】lighthouseAutomated auditing, performance metrics, and best practices for the web.项目地址: https://gitcode.com/GitHub_Trending/lig/lighthouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考