描述在 grunt 中实现暗色模式对比度自动修复

解读

面试官抛出“暗色模式对比度自动修复”这一看似偏视觉的问题,实质想考察三点:

  1. 你是否理解Grunt 插件生态的边界与扩展方式——纯构建工具如何介入设计 token 层;
  2. 你是否能把“对比度修复”拆解成可量化、可自动化的算法步骤,并用 Grunt 任务串起来;
  3. 你是否熟悉国内主流交付场景(微信小程序、Ant Design、企业自建组件库)对暗色模式的合规要求(WCAG 2.1 最低 4.5:1、国企内部要求 7:1)。

一句话:不是让你写视觉稿,而是让你用 Grunt 把“颜色不符合规范”这件事在构建阶段自动消灭,并给出可落地的工程化方案。

知识点

  1. Grunt 任务核心机制grunt.registerMultiTaskthis.files 迭代模型,如何返回异步 done 回调。
  2. 颜色空间计算:将 hex/rgb/hsl 统一转为 CIE LAB 后计算 ΔE 与对比度,避免 RGB 线性误差。
  3. 国内常用设计 token 格式:Ant Design 的 dark.json、腾讯 TDesign 的 theme-dark.js,均为扁平键值对,可直接被 Grunt 读写。
  4. WCAG 2.1 对比度公式(L1 + 0.05) / (L2 + 0.05)4.5:1 为及格线,7:1 为 AAA。
  5. Grunt 插件官方命名规范:必须以 grunt-contrib-grunt- 开头,发布到 npm 时需带关键字 gruntplugin 才能被官方索引收录。
  6. 国内 CI 场景:GitLab-CI 跑在阿里云服务,构建机无图形界面,必须纯 Node 实现,不能依赖 Puppeteer 截图。

答案

  1. 任务定位
    新建一个 grunt-plugin-grunt-contrast-fix,只干三件事:扫描暗色 token → 计算对比度 → 写回修复值。

  2. 插件目录结构

    tasks/
      contrast_fix.js
    lib/
      contrast.js      # 算法核心
    package.json
    
  3. 算法步骤(lib/contrast.js)
    a. 读取 dark.json 扁平对象,得到 { "colorTextBase": "#d3d3d3", "colorBgBase": "#1f1f1f" }
    b. 对任意前景/背景组合,调用 wcagContrast(foreground, background),若比值 < 4.5,进入修复分支
    c. 修复策略:在 CIE LAB 空间保持色相不变,仅同步调整 L 通道,每次步进 ±1,直到对比度 ≥ 4.5 且视觉差 ΔE < 3,防止“跳色”
    d. 返回新色值,并输出 log:colorTextBase 已修正,对比度由 3.2 提升至 4.6

  4. Grunt 任务代码(tasks/contrast_fix.js)

    module.exports = function(grunt) {
      grunt.registerMultiTask('contrast_fix', '自动修复暗色模式对比度', function() {
        const done = this.async();
        const options = this.options({ threshold: 4.5 });
        const { fixPalette } = require('../lib/contrast');
        this.files.forEach(f => {
          const src = f.src[0];
          const tokens = grunt.file.readJSON(src);
          const fixed = fixPalette(tokens, options.threshold);
          grunt.file.write(f.dest, JSON.stringify(fixed, null, 2));
          grunt.log.ok(`已输出修复后的 token 至 ${f.dest}`);
        });
        done();
      });
    };
    
  5. Gruntfile 调用示例

    module.exports = function(grunt) {
      grunt.loadNpmTasks('grunt-contrast-fix');
      grunt.initConfig({
        contrast_fix: {
          dark: {
            options: { threshold: 4.5 },
            files: [{ src: 'src/token/dark.json', dest: 'dist/token/dark.json' }]
          }
        }
      });
      grunt.registerTask('default', ['contrast_fix']);
    };
    
  6. 国内落地细节

    • 将任务前置到 pre-commit 钩子,防止设计师手误提交低对比度 token
    • package.json 中追加 "scripts": {"build:dark": "grunt contrast_fix"},与 vite build 并行,保证构建产物一次性合格,避免测试部回退
    • 若 token 由**飞云(阿里内部)**托管,插件需额外支持 OSS 下载-上传流式处理,避免本地落盘敏感文件
  7. 验证
    运行 npm run build:dark 后,使用 @axe-core/cli 对 dist 目录做扫描,对比度违规项由 47 条降至 0 条,即可向面试官展示量化结果。

拓展思考

  1. 多主题链路:如果企业后续新增“高对比度模式”,可将阈值配置改为数组 [4.5, 7],一次生成多套 token,保持 Grunt 任务单入口多出口,减少 CI 时间。
  2. 与 Figma 插件联动:国内团队常用即时设计(js.design)替代 Figma,其开放 API 可导出 JSON token。让 Grunt 任务通过 WebSocket 监听设计稿更新事件,实现“设计侧一改,构建侧立即修复”,把对比度问题消灭在设计师保存之前
  3. 灰度发布:在腾讯系小程序环境,暗色模式开关由后台配置。Grunt 任务可追加版本号染色,将修复后的 token 打入独立 chunk,通过cdn 灰度按用户尾号 10% 放量,对比度异常埋点上报,验证无客诉后再全量,体现前端工程化与运维灰度的深度结合。