使用 grunt 注入键盘陷阱检测脚本

解读

“键盘陷阱”指网页中某些可聚焦元素(富文本编辑器、模态框、自定义组件等)一旦获得焦点后,用户仅靠键盘(Tab/Shift+Tab)无法跳出,造成可访问性事故。
面试官想考察:

  1. 能否用 Grunt 在“构建阶段”把一段 检测脚本 自动注入到所有(或指定)HTML 文件;
  2. 注入后是否能在 运行时 实时报告陷阱位置、给出修复建议;
  3. 整个流程要符合国内 合规性能 要求:不污染全局、可开关、可溯源、可回滚。
    该任务本质是“构建时静态插桩 + 运行时监控”,属于前端质量保障的高级实践,难度对标 资深/专家 岗位。

知识点

  1. Grunt 任务生命周期:init → config → registerTask → run;
  2. grunt-contrib-copygrunt-string-replacinggrunt-injector 等插件的“transform”钩子;
  3. AST 级安全注入:使用 cheerio 解析 HTML,只在 <body> 末尾插入一次,避免重复;
  4. 键盘陷阱判定算法
    • 监听 keydown 捕获阶段,记录 Tab 路径;
    • 若焦点在 document 内循环超过 N 次(默认 3)仍无法离开指定区域,则判定为陷阱;
    • 通过 MutationObserver 动态监听 DOM 变化,防止异步渲染漏报;
  5. 国内可访问性规范:GB/T 37668-2019《信息技术 互联网内容无障碍可访问性技术要求》2.1.4 章节对键盘可操作性的强制要求;
  6. 性能与隐私:检测脚本体积 < 3 kB(gzip),默认 关闭,通过 localStorage 或 URL 参数 ?a11y=1 开启;
  7. CI 集成:在 Jenkins/GitLab CI 中把该 Grunt 任务作为 门禁项,若检测到陷阱则 非零退出码 阻断发布。

答案

  1. 安装依赖
npm i -D grunt grunt-contrib-copy grunt-string-replacing cheerio
  1. 编写检测脚本 src/js/a11y-trap-monitor.js(核心逻辑已压缩成 IIFE,对外暴露 window.__A11Y_TRAP_MONITOR__.toggle()
  2. Gruntfile.js 配置
module.exports = function(grunt) {
  grunt.initConfig({
    // 1. 先拷贝模板到临时目录,避免污染源码
    copy: {
      a11y: {
        expand: true,
        cwd: 'src/',
        src: '**/*.html',
        dest: '.tmp/'
      }
    },
    // 2. 注入脚本
    'string-replacing': {
      a11y: {
        files: [{ expand:true, cwd:'.tmp/', src:'**/*.html', dest:'dist/' }],
        options: {
          replacements: [{
            pattern: '</body>',
            replacement: function(match, srcPath) {
              const fs   = require('fs');
              const code = fs.readFileSync('src/js/a11y-trap-monitor.js', 'utf-8');
              // 内联后加哈希,方便溯源
              const hash = require('crypto').createHash('md5').update(code).digest('hex').slice(0,8);
              return `<script data-a11y-trap-hash="${hash}">${code}</script>\n</body>`;
            }
          }]
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-string-replacing');

  grunt.registerTask('a11y', ['copy:a11y', 'string-replacing:a11y']);
  grunt.registerTask('default', ['a11y']);
};
  1. 运行时
    • 开发环境访问 http://localhost:3000/?a11y=1,控制台实时打印陷阱节点 XPath;
    • 生产环境默认关闭,可通过 运维开关 动态开启,满足 等保测评无障碍合规抽查
  2. 回滚
    • 若注入脚本引发性能争议,只需在 Gruntfile 中注释掉 string-replacing 任务,重新构建即可 秒级回滚,无需改业务代码。

拓展思考

  1. 双引擎方案:对 Vue/React 单页应用,可结合 webpacka11y-trap-loaderGrunt 的静态 HTML 注入形成 混合构建,保证 老项目新项目 统一标准。
  2. 量化指标:在 Grunt 任务后追加 自定义 reporter,把陷阱数量、XPath、修复建议写入 a11y-report.json,上传到 内部无障碍平台月度排名,推动业务线 零陷阱 KPI。
  3. 灰度与埋点:利用 国内主流埋点 SDK(如 神策、GrowingIO)把陷阱触发事件埋入 a11y_trap_detect 指标,结合 用户画像 分析视障群体真实受阻率,为 产品迭代 提供数据支撑。