解释在 grunt 中实现无障碍阈值门禁
解读
“无障碍阈值门禁”在国内前端交付流程里通常指:在代码进入测试或预发布环境前,必须跑一遍无障碍自动化检测,且错误分、警告分、颜色对比分等关键指标必须达到产品、法务或客户预设的最低分数线(阈值),否则中断构建并拒绝打包。
Grunt 作为老牌任务运行器,本身不提供无障碍引擎,但可以通过官方插件机制把** axe-core / lighthouse / accessibility-developer-tools 等引擎集成进来,再借助 grunt-contrib-qunit、grunt-exec、grunt-if、grunt-fail 等辅助插件做分数断言与流程门禁**。
面试时,面试官想确认三点:
- 你是否了解国内监管对信息无障碍的合规要求(工信部、中残联、GB/T 37668、GB/T 29799 等标准)。
- 能否在 Grunt 生态里闭环“扫描→评分→断言→阻断”四步。
- 是否具备可维护性思维:阈值可配置、报告可回溯、错误可定位。
知识点
- axe-core:开源无障碍规则引擎,支持 4 级严重度(minor、moderate、serious、critical),可输出 JSON 分数。
- grunt-axe-runner:社区封装,把 axe-core 塞进 PhantomJS(或 headless Chrome),直接扫描本地构建产物。
- lighthouse-ci:Google 官方 CLI,可只跑 accessibility 类别,返回 0~100 分。
- grunt-exec:通用 shell 插件,用于调用 lighthouse-ci --chrome-flags="--headless" 并保存报告。
- grunt-jsonlint 与 grunt-json-filter:解析 axe 或 lighthouse 报告,提取 violations.length、score、impact 等字段。
- grunt-if 或 grunt-fail:做条件判断,若分数低于阈值,立即 grunt.fail.fatal() 中断后续任务。
- 阈值配置化:把“警告≤3 且严重≤0 且总分≥90”写在 Gruntfile.js 的 grunt.initConfig 外部 JSON,方便运维或 QA 在 Jenkins/GitLab CI 里动态注入,无需改代码。
- 国内合规映射:
- critical 对应 GB/T 37668 A 级阻断项
- serious 对应 AA 级
- 颜色对比失败直接记为 A 级阻断
- 报告归档:用 grunt-contrib-copy 把 axe-report.json 和 lighthouse.html 推到 minio 或阿里云 OSS,供法务审计。
- 性能兼顾:axe 扫描只跑关键页面(首页、登录、支付),通过 grunt-config 的 files 数组和 grunt-newer 减少重复扫描。
答案
下面给出一条可直接落地、符合国内合规要求的 Grunt 任务链,阈值门禁写在 a11yThreshold.json,任何一项不达标即中断构建。
- 安装依赖
npm i -D grunt-axe-runner grunt-exec grunt-json-filter grunt-if grunt-fail load-grunt-tasks
- 阈值文件
a11yThreshold.json(由 QA 在 CI 变量里覆盖)
{
"critical": 0,
"serious": 0,
"moderate": 3,
"minor": 10,
"score": 90
}
- Gruntfile.js 核心片段
module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
const threshold = grunt.file.readJSON('a11yThreshold.json');
grunt.initConfig({
// 步骤1:用 axe 扫描构建产物
axe: {
main: {
options: {
urls: ['http://localhost:8080/dist/index.html'],
dest: 'reports/axe-report.json'
}
}
},
// 步骤2:提取关键字段
json_filter: {
axe: {
src: 'reports/axe-report.json',
dest: 'reports/axe-summary.json',
filter: function(list) {
const summary = { critical:0, serious:0, moderate:0, minor:0 };
list.forEach(v => summary[v.impact]++);
summary.score = Math.max(0, 100 - (summary.critical*10 + summary.serious*5 + summary.moderate*2 + summary.minor));
return summary;
}
}
},
// 步骤3:阈值断言
if: {
axeGate: {
options: {
test: function() {
const s = grunt.file.readJSON('reports/axe-summary.json');
return s.critical > threshold.critical ||
s.serious > threshold.serious ||
s.moderate > threshold.moderate ||
s.minor > threshold.minor ||
s.score < threshold.score;
},
ifTrue: ['fail:axeFailed']
}
}
},
// 步骤4:中断构建
fail: {
axeFailed: {
message: '**无障碍阈值门禁未通过,请查看 reports/axe-report.json 并修复后再提交**'
}
}
});
// 注册流水线
grunt.registerTask('a11y', ['axe', 'json_filter', 'if:axeGate']);
grunt.registerTask('default', ['build', 'connect', 'a11y']);
};
- Jenkinsfile(国内常用)
stage('无障碍门禁') {
steps {
sh 'npx grunt a11y --threshold=' + params.A11Y_THRESHOLD
}
}
至此,任何 critical>0 或总分<90 都会触发 grunt.fail.fatal(),Jenkins 构建红色失败,代码无法进入提测环节,实现真正的“门禁”而非“提醒”。
拓展思考
- 多引擎互补:axe 擅长 DOM 静态规则,lighthouse 能测颜色对比和 tab order,可在 Grunt 里并行跑
grunt.concurrent,取最低分作为最终门禁,避免漏检。 - 增量扫描:结合
git diff解析出本次 MR 影响的页面,通过grunt-contrib-watch只扫增量,把扫描时间从 5min 降到 30s,适合大型站点。 - 规则白名单:国内项目常含第三方地图、视频组件,无法改源码;可在
axe.run的rules参数里禁用 color-contrast,同时在注释里加// a11y-disable-next-line,并配套 eslint-plugin-a11y-disable 做代码审计,防止滥用。 - 合规报告双轨:一份 JSON 给 CI 做阈值判断,一份中文 HTML 给法务留档,HTML 里要反标 GB/T 37668 条款编号,方便外审。
- 灰度降级:上线前若因时间压力必须降级,可在 Gruntfile 里读
process.env.A11Y_BREAKBUILD,false 时把 fail 任务换成 warn,实现“红灯变黄灯”,但要求次日 stand-up 必须给出修复排期,兼顾敏捷与合规。