自定义审计系统的设计与前端实现经验分享
又踩坑了,自定义审计规则不生效
上周在搞一个内部工具的性能优化,想用 Lighthouse 的自定义审计(Custom Audit)来检测我们自己的一些业务指标。结果折腾了半天,写好的规则死活不跑,Lighthouse 报告里压根没出现我加的那项。一开始以为是配置写错了,后来发现是整个思路都歪了。
这里我踩了个大坑:我以为只要在 audits 里注册了,Lighthouse 就会自动执行。结果不是。它得配合 categories 和 config 才能真正跑起来。文档里其实写了,但我没细看,直接照着官方示例改了一点就跑,当然不行。
试了三种方法,前两种都翻车了
第一次,我直接在 lighthouse-plugin 里导出一个 audit 类,然后在 plugin.js 里注册。跑完发现控制台报错:audit not found。查了下,是因为我没在 config 里显式引用这个 audit 的路径。Lighthouse 不会自动扫描目录,必须手动指定。
第二次,我补上了 config,但还是没显示。打开 DevTools 看 Lighthouse 的 debug 日志,发现我的 audit 被跳过了,原因是 inapplicable。这说明 audit.meta.requiredArtifacts 里声明的 artifact 没有被收集到。我用了 ConsoleMessages,但默认 config 里没开这个 gatherer。得在 settings 或 config 里显式启用。
折腾了半天才发现,自定义审计不是“写完就能跑”,而是要完整走通:gatherer → artifact → audit → category 这条链。中间断一环,就静默失败,连个错误提示都没有,贼坑。
核心代码就这几行,但细节全是坑
最后理清楚了,完整的自定义审计需要三部分:audit 文件、plugin 配置、以及调用时的 config。下面是我最终能跑通的最小可运行版本。
先写 audit 本身,比如我想检测页面有没有调用某个废弃的 API:
// audits/no-deprecated-api.js
const Audit = require('lighthouse').Audit;
class NoDeprecatedApiAudit extends Audit {
static get meta() {
return {
id: 'no-deprecated-api',
title: 'Avoid deprecated APIs',
failureTitle: 'Uses deprecated APIs',
description: 'Checks if the page calls any deprecated internal APIs.',
requiredArtifacts: ['ConsoleMessages'], // 关键!必须声明依赖的 artifact
scoreDisplayMode: Audit.SCORING_MODES.BINARY,
};
}
static assessConsoleMessages(consoleMessages) {
const deprecatedCalls = consoleMessages.filter(msg =>
msg.text && msg.text.includes('DEPRECATED_API_CALL')
);
return {
usesDeprecated: deprecatedCalls.length > 0,
messages: deprecatedCalls.map(m => m.text),
};
}
static async audit(artifacts) {
const consoleMessages = artifacts.ConsoleMessages;
const result = this.assessConsoleMessages(consoleMessages);
return {
score: result.usesDeprecated ? 0 : 1,
details: result.usesDeprecated ? {
type: 'list',
items: result.messages.map(msg => ({ text: msg }))
} : undefined,
};
}
}
module.exports = NoDeprecatedApiAudit;
然后写 plugin 配置,把 audit 挂进去:
// plugin.js
module.exports = {
audits: [
require.resolve('./audits/no-deprecated-api.js'),
],
category: {
title: 'Custom Checks',
description: 'Our internal quality gates',
auditRefs: [
{ id: 'no-deprecated-api', weight: 1, group: 'perf' },
],
},
};
最后,调用 Lighthouse 时必须传入包含这个 plugin 的 config。注意:不能只靠 --plugins 参数,因为默认 config 可能没包含你需要的 gatherers。我这里显式合并了 defaultConfig 并启用了 ConsoleMessages:
// runner.js
const lighthouse = require('lighthouse');
const defaultConfig = require('lighthouse/lighthouse-core/config/default-config.js');
const customConfig = {
extends: defaultConfig,
settings: {
...defaultConfig.settings,
// 确保 ConsoleMessages 被收集
gatherers: [
...(defaultConfig.settings.gatherers || []),
'console-messages',
],
},
plugins: ['path/to/your/plugin'],
};
(async () => {
const url = 'https://jztheme.com/test-page';
const opts = { output: 'html' };
const runnerResult = await lighthouse(url, opts, customConfig);
console.log(runnerResult.lhr.categories['custom-checks']);
})();
这里注意我踩过好几次坑:gatherers 字段必须显式加上 'console-messages',否则 ConsoleMessages artifact 就是空的,audit 直接跳过。而且 plugin 的路径必须用 require.resolve,不然 Lighthouse 找不到模块。
为什么这么绕?说说背后的原理
Lighthouse 的架构是分阶段的:先 gather(收集原始数据),再 audit(分析数据),最后 categorize(归类打分)。自定义 audit 只负责第二步,但它依赖第一步产出的 artifact。如果你没在 config 里启用对应的 gatherer,第一步就空跑,第二步自然没数据。
比如 ConsoleMessages 这个 artifact,是由 console-messages gatherer 生成的。而默认 config 为了性能,默认不收集所有可能的 artifact。所以你得手动加回去。这设计其实合理,但文档藏得太深,新手很容易卡住。
另外,plugin 的 category 字段不会自动合并到主报告里,必须通过 auditRefs 引用已注册的 audit。而且 id 必须和 audit 里的 meta.id 完全一致,大小写敏感。我之前写成 NoDeprecatedApi,结果对不上,又浪费半小时。
改完后还有一两个小问题,但无大碍
现在 audit 能正常跑了,但在 Lighthouse CLI 里输出的 detail 列表样式有点丑,文字换行不太对。不过因为我们主要是用 JSON 结果做自动化检查,UI 不重要,就没深究。
另一个问题是,如果页面没触发任何 console.log,ConsoleMessages 会是空数组,没问题;但如果页面有 CSP 阻止了 console 访问,可能会收不到消息。不过这种情况在我们的内网环境几乎不存在,暂时忽略。
总的来说,虽然过程曲折,但一旦跑通,后续加新规则就快了。现在我们团队已经用这个机制加了五六条自定义规则,比如检测未压缩的图片、未懒加载的组件等,集成到 CI 里做质量门禁,效果不错。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有办法自动注入 gatherers?或者用更简洁的方式组织 plugin?我现在这个结构还是有点啰嗦。

暂无评论