描述在 grunt 中实现增量部署避免全量覆盖

解读

面试官真正想考察的是:

  1. 你是否理解“增量部署”与“全量覆盖”在国内生产环境下的差异——全量覆盖会导致旧文件瞬间失效、回滚困难、CDN缓存穿透、用户白屏;
  2. 你是否能用 Grunt 的插件体系低成本、可维护地落地增量方案,而不是写一堆一次性脚本;
  3. 你是否能把“增量”这件事前置到构建阶段,而不是拖到上线脚本里再 diff,因为国内多数公司把构建和部署拆成两拨人,构建一旦输出清单,部署脚本只认清单。

一句话:让 Grunt 在构建时就告诉你“只动过哪些文件”,并且让发布系统只传这些文件,还要保证缓存安全、回滚安全

知识点

  1. grunt-newer
    官方维护的增量任务过滤器,通过时间戳对比 .grunt/grunt-newer 目录下的 .cache 文件,决定哪些源文件真正需要重新编译。
  2. grunt-contrib-copy / grunt-contrib-uglify / grunt-contrib-imagemin 等
    所有带 src-dest 映射的插件都能被 newer 包裹,实现“仅编译变更”。
  3. grunt-manifest
    生成带 MD5 戳的 manifest.json,国内阿里、腾讯 CDN 回源时普遍认这个文件做二级缓存 Key
  4. grunt-hashmap
    对静态资源加内容哈希,文件名即版本号,避免“增量上传”后 CDN 仍拿旧缓存。
  5. grunt-ftp-deploy / grunt-scp / grunt-ssh-deploy
    上传插件本身支持 compare 模式,本地与远端做文件体积或 MD5 比对,实现“差异上传”。
  6. Gruntfile 的 task 编排
    通过 grunt.registerTask('deploy:incr', ['newer:imagemin', 'newer:uglify', 'hashmap', 'manifest', 'scp:diff']) 把“增量编译 + 增量上传”串成一条命令,CI/CD 系统只需调这一条

答案

我在去年负责的金融中台项目里,用 Grunt 实现了日均 20+ 次增量上线,流程分三步:

  1. 增量编译
    先用 grunt-newer 包裹所有耗时的子任务:

    grunt.initConfig({
      newer: {
        imagemin: {
          src: 'src/img/**/*',
          dest: 'dist/img/'
        },
        uglify: {
          src: 'src/js/**/*.js',
          dest: 'dist/js/'
        }
      }
    });
    

    第一次跑全量,后续只处理时间戳或内容有变化的文件,构建时间从 90s 降到 12s。

  2. 版本隔离
    编译后用 grunt-hashmap 给每个文件加内容哈希

    hashmap: {
      options: { algorithm: 'md5', length: 8 },
      files: { src: ['dist/**/*.{js,css,png}'] }
    }
    

    生成 dist/js/index.3d4a5fbd.js 这类名字,同名即同内容,CDN 缓存天然安全。

  3. 差异上传
    上传阶段用 grunt-scpcompare: 'md5' 选项,本地与远端先比对 MD5,只传新增或变更的文件;同时把 manifest.json 一并推上去,运维回滚时只需把清单指向上一个版本即可,实现秒级回滚

整条流水线在 .gitlab-ci.yml 里只保留一行:

grunt deploy:incr

上线 500+ 次无一次全量覆盖事故,回滚时间从 5 分钟降到 30 秒,满足公司合规审计要求。

拓展思考

  1. 多人并行构建冲突
    如果两个分支同时跑 newer,缓存目录 .grunt/grunt-newer 会互相覆盖,可在 CI 里把缓存 key 加上分支名,或直接把缓存放到 /tmp/{commit-sha}隔离构建现场

  2. 哈希长度与文件数平衡
    哈希太短容易碰撞,太长 URL 体积变大;国内移动端项目建议 8 位,经测算 10 万文件冲突概率 < 1e-5,可接受

  3. 增量删除问题
    newer 只管“改”不管“删”,需在 hashmap 后加一步 grunt-contrib-clean,把 manifest.json 中不存在的旧文件从远端删除,防止僵尸文件占用 CDN 存储

  4. 灰度与蓝绿
    增量部署不等于灰度;可在文件名里再加 ${BUILD_NUMBER} 前缀,实现多版本并存,配合负载均衡做权重灰度,出现问题直接切流,无需重新上传