解释在 grunt 中实现无障碍阈值门禁

解读

“无障碍阈值门禁”在国内前端交付流程里通常指:在代码进入测试或预发布环境前,必须跑一遍无障碍自动化检测,且错误分、警告分、颜色对比分等关键指标必须达到产品、法务或客户预设的最低分数线(阈值),否则中断构建并拒绝打包
Grunt 作为老牌任务运行器,本身不提供无障碍引擎,但可以通过官方插件机制把** axe-core / lighthouse / accessibility-developer-tools 等引擎集成进来,再借助 grunt-contrib-qunit、grunt-exec、grunt-if、grunt-fail 等辅助插件做分数断言与流程门禁**。
面试时,面试官想确认三点:

  1. 你是否了解国内监管对信息无障碍的合规要求(工信部、中残联、GB/T 37668、GB/T 29799 等标准)。
  2. 能否在 Grunt 生态里闭环“扫描→评分→断言→阻断”四步。
  3. 是否具备可维护性思维:阈值可配置、报告可回溯、错误可定位。

知识点

  1. axe-core:开源无障碍规则引擎,支持 4 级严重度(minor、moderate、serious、critical),可输出 JSON 分数。
  2. grunt-axe-runner:社区封装,把 axe-core 塞进 PhantomJS(或 headless Chrome),直接扫描本地构建产物。
  3. lighthouse-ci:Google 官方 CLI,可只跑 accessibility 类别,返回 0~100 分。
  4. grunt-exec:通用 shell 插件,用于调用 lighthouse-ci --chrome-flags="--headless" 并保存报告。
  5. grunt-jsonlint 与 grunt-json-filter:解析 axe 或 lighthouse 报告,提取 violations.length、score、impact 等字段。
  6. grunt-if 或 grunt-fail:做条件判断,若分数低于阈值,立即 grunt.fail.fatal() 中断后续任务。
  7. 阈值配置化:把“警告≤3 且严重≤0 且总分≥90”写在 Gruntfile.js 的 grunt.initConfig 外部 JSON,方便运维或 QA 在 Jenkins/GitLab CI 里动态注入,无需改代码。
  8. 国内合规映射
    • critical 对应 GB/T 37668 A 级阻断项
    • serious 对应 AA 级
    • 颜色对比失败直接记为 A 级阻断
  9. 报告归档:用 grunt-contrib-copy 把 axe-report.json 和 lighthouse.html 推到 minio 或阿里云 OSS,供法务审计。
  10. 性能兼顾:axe 扫描只跑关键页面(首页、登录、支付),通过 grunt-config 的 files 数组grunt-newer 减少重复扫描。

答案

下面给出一条可直接落地、符合国内合规要求的 Grunt 任务链,阈值门禁写在 a11yThreshold.json任何一项不达标即中断构建

  1. 安装依赖
npm i -D grunt-axe-runner grunt-exec grunt-json-filter grunt-if grunt-fail load-grunt-tasks
  1. 阈值文件 a11yThreshold.json(由 QA 在 CI 变量里覆盖)
{
  "critical": 0,
  "serious": 0,
  "moderate": 3,
  "minor": 10,
  "score": 90
}
  1. 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']);
};
  1. Jenkinsfile(国内常用)
stage('无障碍门禁') {
  steps {
    sh 'npx grunt a11y --threshold=' + params.A11Y_THRESHOLD
  }
}

至此,任何 critical>0 或总分<90 都会触发 grunt.fail.fatal(),Jenkins 构建红色失败,代码无法进入提测环节,实现真正的“门禁”而非“提醒”。

拓展思考

  1. 多引擎互补:axe 擅长 DOM 静态规则,lighthouse 能测颜色对比和 tab order,可在 Grunt 里并行跑 grunt.concurrent,取最低分作为最终门禁,避免漏检。
  2. 增量扫描:结合 git diff 解析出本次 MR 影响的页面,通过 grunt-contrib-watch 只扫增量,把扫描时间从 5min 降到 30s,适合大型站点。
  3. 规则白名单:国内项目常含第三方地图、视频组件,无法改源码;可在 axe.runrules 参数里禁用 color-contrast,同时在注释里加 // a11y-disable-next-line,并配套 eslint-plugin-a11y-disable 做代码审计,防止滥用。
  4. 合规报告双轨:一份 JSON 给 CI 做阈值判断,一份中文 HTML 给法务留档,HTML 里要反标 GB/T 37668 条款编号,方便外审。
  5. 灰度降级:上线前若因时间压力必须降级,可在 Gruntfile 里读 process.env.A11Y_BREAKBUILDfalse 时把 fail 任务换成 warn,实现“红灯变黄灯”,但要求次日 stand-up 必须给出修复排期,兼顾敏捷与合规。