解释在 grunt 中实现 feature flag 注入的两种方案

解读

国内一线团队面试时,这道题考察的是“在不改业务源码的前提下,如何借助 Grunt 把开关变量注入到产物里”,从而支持灰度发布、A/B Test、环境隔离。回答必须体现出“构建阶段注入”而非“运行时拉取”,并给出两种可落地的 Grunt 插件级方案,同时说明各自优缺点与典型使用场景。

知识点

  1. 构建时预编译常量:在打包阶段把占位符替换成布尔值或对象,产物中无多余请求,性能最好。
  2. 文件内联替换:借助 grunt-string-replace、grunt-replace 等通用文本替换插件,把形如 __FEATURE_X__ 的标记一次性替换成 true/false。
  3. AST 级注入:使用 grunt-babel-plugin 或 grunt-contrib-uglify 的自定义 transform,在语法树里插入 window.FF = {…},保证压缩后仍能被业务代码引用。
  4. 环境隔离:通过 grunt.template.process 读取 process.env.NODE_ENV 或自定义 grunt.option('env'),实现“同一份 Gruntfile,不同命令出不同包”。
  5. 缓存与增量编译:替换方案需兼容 grunt-contrib-watch,避免每次全量打包导致 dev 体验下降。

答案

在真实项目中,我落地过以下两种主流方案,均通过 Grunt 完成“零运行时请求”的 feature flag 注入:

方案一:字符串模板替换(grunt-string-replace)

  1. 在源码里预留占位符,如
    if (__ENABLE_PAY__) { /* 支付逻辑 */ }
  2. 在 Gruntfile 中配置 grunt-string-replace 任务:
    stringReplace: {
      dist: {
        files: [{ expand: true, cwd: 'src', src: '**/*.js', dest: 'dist' }],
        options: {
          replacements: [{
            pattern: /__ENABLE_PAY__/g,
            replacement: grunt.option('env') === 'prod' ? 'false' : 'true'
          }]
        }
      }
    }
    
  3. 构建命令 grunt build --env=prod 即可产出带关闭开关的包。
    优点:配置简单、无学习成本;缺点:只能做纯文本替换,压缩后若被重命名可能失效,需配合 /*#__PURE__*/ 注释保留。

方案二:AST 级全局常量定义(grunt-babel + babel-plugin-inline-replace-variables)

  1. 安装 grunt-babel 及自定义 babel 插件,在 .babelrc 中声明:
    { "plugins": [["inline-replace-variables", { "__FF__": { "pay": false, "login": true } }]] }
    
  2. Gruntfile 中通过 grunt.config.set('babel.options.env', { flags: require('./flag.' + grunt.option('env')) }) 动态把环境变量喂给 babel。
  3. 业务代码直接写 if (__FF__.pay) {…},构建后自动编译成 if (false){…},uglify 阶段会删除死代码,包体积最小。
    优点:语法树级别,安全且可 tree-shaking;缺点:需要团队熟悉 Babel 插件开发,首次配置成本略高。

两种方案都能与国内常见的蓝绿发布、钉钉/飞书机器人通知流水线无缝集成,选择时根据“是否需要死代码消除”与“团队 Babel 技术储备”权衡即可。

拓展思考

  1. 灰度动态化:如果业务要求“上线后仍可实时调整开关”,可在 Grunt 构建阶段只注入“取号逻辑”window.FF = <%= grunt.file.read('remoteFlag.json') %>,然后接入公司自研的“配置中心 SDK”,实现“构建+远程”混合策略。
  2. 多产物并行:在 GitLab-CI 里并行跑 grunt build --env=graygrunt build --env=prod,产出两份包,由 nginx 根据 cookie 分流,Grunt 侧只需保证两次构建缓存隔离即可。
  3. 合规与审计:国内金融项目需留痕,可在替换同时把最终注入的 flag 快照写入 dist/manifest.flag.json,由运维归档,方便监管回查。