集成 grunt-storybook 与 chromatic 实现 UI 快照比对

解读

在国内前端团队普遍追求“视觉回归零漏测”的背景下,面试官抛出该题,核心想验证三件事:

  1. 你是否理解 Grunt 插件生态的“胶水”角色——grunt-storybook 只是调用 Storybook 的 CLI,而真正的快照比对由 Chromatic 负责;
  2. 能否把公司内网、CI 流水线、Monorepo、私有 NPM 源等中国特有约束考虑进去,给出可落地的配置;
  3. 是否具备成本与安全意识:Chromatic 按快照计费,国内项目动辄上千组件,如何按需 diff、并行分片、失败重试,都是落地难点。

因此,回答要体现“能用、敢用、用得省”的工程化思维,而不是简单跑通命令。

知识点

  1. grunt-storybook 本质:Grunt 多任务包装器,把 start-storybookbuild-storybook 转成 Grunt 任务,不自带快照能力
  2. Chromatic 双模式
    • Cloud 模式:官方 SaaS,国内需配置 HTTPS_PROXYchromatic@1.2.0 之后支持的 --upload-metadata=false 规避 GFW 上传超时;
    • Self-hosted 模式:企业版可部署在阿里云 ACK,镜像走私有 Harbor,快照存储在 OSS,解决合规。
  3. 快照比对原理:Chromatic 把每个 Story 渲染成 DOM+CSSOM 快照,生成 base64 图片,MurMurHash 去重;仅当“像素差异率 > 0.06%”才标为变更,支持 Ant Design 字体抗锯齿等阈值调优。
  4. Gruntfile 关键钩子
    • before_chromatic:先跑 grunt-contrib-clean 删旧静态资源,避免幽灵快照
    • after_chromatic:用 grunt-json-generator 把返回的 buildIdchangeCount 写回 dist/stats.json,供后续钉钉机器人通知。
  5. CI 集成要点
    • GitLab-CI 国内 Runner 需加 yarn --registry https://registry.npmmirror.com --network-timeout 600000
    • 并行分片:利用 chromatic --only-changed --externals="**/node_modules/**"把 Monorepo 子包 diff 范围缩小 70%
    • 失败重试:grunt-fail-once 插件捕获非零退出码,最多重试 3 次,防止弱网误报

答案

  1. 安装依赖
# 私有源加速
yarn add -D grunt-storybook chromatic grunt-exec grunt-contrib-clean
  1. Gruntfile.js 核心片段
module.exports = function(grunt) {
  grunt.initConfig({
    clean: { chromatic: 'storybook-static' },

    exec: {
      chromatic: {
        cmd: `npx chromatic \
               --project-token=${process.env.CHROMATIC_PROJECT_TOKEN} \
               --storybook-build-dir=storybook-static \
               --exit-zero-on-changes \
               --only-changed \
               --externals=**/node_modules/**`,
        maxBuffer: 1024 * 1024 * 10 // 10MB,防大组件 OOM
      }
    },

    storybook: {
      build: { outputDir: 'storybook-static', configDir: '.storybook' }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-exec');
  grunt.loadNpmTasks('grunt-storybook');

  grunt.registerTask('ui-snapshot', [
    'clean:chromatic',
    'storybook:build',
    'exec:chromatic'
  ]);
};
  1. GitLab-CI 片段(国内镜像)
ui-snapshot:
  stage: visual-test
  image: registry.cn-hangzhou.aliyuncs.com/company/node:16-chrome
  variables:
    CHROMATIC_PROJECT_TOKEN: $CHROMATIC_PROJECT_TOKEN
    HTTPS_PROXY: http://proxy.company.com:3128
  script:
    - yarn --frozen-lockfile --registry https://registry.npmmirror.com
    - grunt ui-snapshot --gruntfile=build/Gruntfile.js
  retry: 2
  only:
    - merge_requests
  artifacts:
    reports:
      junit: dist/chromatic.xml
  1. 费用与合规兜底
  • 在 MR 描述中强制添加 [chromatic-skip] 标签,Gruntfile 里用 grunt.option('chromatic-skip') 跳过快照,节省 30% 额度
  • 涉密组件(如后台账单页)在 .storybook/main.jsstories: ['!**/*confidential*.stories.js']防止上传到境外 SaaS

拓展思考

  1. 渐进式替换:若公司未来迁往 Vite,可用 grunt-vitepress 做静态构建,chromatic 命令保持不变,只需把 storybook-build-dir 指向 dist/vite-static验证 Grunt 作为任务编排器可平滑过渡
  2. 国产替代方案:若政策要求数据不出境,可调研 “视觉中国” 自研 Applitools 镜像,通过 grunt-exec 调用其 CLI,把 diff 结果回写 GitLab MR,实现国产化闭环
  3. 智能降噪:对动画组件,可在 Story 级 parameters: { chromatic: { pauseAnimationAtEnd: true } }避免 GIF 误报;结合 grunt-replace 在构建前自动注入该参数,实现 0 人工干预