描述在 grunt 中实现增量部署避免全量覆盖
解读
面试官真正想考察的是:
- 你是否理解“增量部署”与“全量覆盖”在国内生产环境下的差异——全量覆盖会导致旧文件瞬间失效、回滚困难、CDN缓存穿透、用户白屏;
- 你是否能用 Grunt 的插件体系低成本、可维护地落地增量方案,而不是写一堆一次性脚本;
- 你是否能把“增量”这件事前置到构建阶段,而不是拖到上线脚本里再 diff,因为国内多数公司把构建和部署拆成两拨人,构建一旦输出清单,部署脚本只认清单。
一句话:让 Grunt 在构建时就告诉你“只动过哪些文件”,并且让发布系统只传这些文件,还要保证缓存安全、回滚安全。
知识点
- grunt-newer
官方维护的增量任务过滤器,通过时间戳对比.grunt/grunt-newer目录下的.cache文件,决定哪些源文件真正需要重新编译。 - grunt-contrib-copy / grunt-contrib-uglify / grunt-contrib-imagemin 等
所有带src-dest映射的插件都能被newer包裹,实现“仅编译变更”。 - grunt-manifest
生成带 MD5 戳的manifest.json,国内阿里、腾讯 CDN 回源时普遍认这个文件做二级缓存 Key。 - grunt-hashmap
对静态资源加内容哈希,文件名即版本号,避免“增量上传”后 CDN 仍拿旧缓存。 - grunt-ftp-deploy / grunt-scp / grunt-ssh-deploy
上传插件本身支持compare模式,本地与远端做文件体积或 MD5 比对,实现“差异上传”。 - Gruntfile 的 task 编排
通过grunt.registerTask('deploy:incr', ['newer:imagemin', 'newer:uglify', 'hashmap', 'manifest', 'scp:diff'])把“增量编译 + 增量上传”串成一条命令,CI/CD 系统只需调这一条。
答案
我在去年负责的金融中台项目里,用 Grunt 实现了日均 20+ 次增量上线,流程分三步:
-
增量编译
先用grunt-newer包裹所有耗时的子任务:grunt.initConfig({ newer: { imagemin: { src: 'src/img/**/*', dest: 'dist/img/' }, uglify: { src: 'src/js/**/*.js', dest: 'dist/js/' } } });第一次跑全量,后续只处理时间戳或内容有变化的文件,构建时间从 90s 降到 12s。
-
版本隔离
编译后用grunt-hashmap给每个文件加内容哈希:hashmap: { options: { algorithm: 'md5', length: 8 }, files: { src: ['dist/**/*.{js,css,png}'] } }生成
dist/js/index.3d4a5fbd.js这类名字,同名即同内容,CDN 缓存天然安全。 -
差异上传
上传阶段用grunt-scp的compare: 'md5'选项,本地与远端先比对 MD5,只传新增或变更的文件;同时把manifest.json一并推上去,运维回滚时只需把清单指向上一个版本即可,实现秒级回滚。
整条流水线在 .gitlab-ci.yml 里只保留一行:
grunt deploy:incr
上线 500+ 次无一次全量覆盖事故,回滚时间从 5 分钟降到 30 秒,满足公司合规审计要求。
拓展思考
-
多人并行构建冲突
如果两个分支同时跑newer,缓存目录.grunt/grunt-newer会互相覆盖,可在 CI 里把缓存 key 加上分支名,或直接把缓存放到/tmp/{commit-sha},隔离构建现场。 -
哈希长度与文件数平衡
哈希太短容易碰撞,太长 URL 体积变大;国内移动端项目建议 8 位,经测算 10 万文件冲突概率 < 1e-5,可接受。 -
增量删除问题
newer只管“改”不管“删”,需在hashmap后加一步grunt-contrib-clean,把manifest.json中不存在的旧文件从远端删除,防止僵尸文件占用 CDN 存储。 -
灰度与蓝绿
增量部署不等于灰度;可在文件名里再加${BUILD_NUMBER}前缀,实现多版本并存,配合负载均衡做权重灰度,出现问题直接切流,无需重新上传。