描述在 grunt 中实现子应用版本互锁文件

解读

在国内微前端、多仓库(Monorepo/Multi-repo)场景下,子应用版本互锁指:当 A、B、C 三个子应用共同组成一个平台时,它们的 package.json 中互相引用的版本号必须保持严格一致,否则会出现“线上主应用拉取到不兼容子应用资源”的严重故障。
面试官问“如何用 Grunt 实现”,并不是让你手写一个依赖管理器,而是考察:

  1. 能否用 Grunt 的任务编排 + 文件处理能力把“版本号一致性检查→自动修正→生成锁文件”做成一条标准化流水线;
  2. 是否理解锁文件package-lock.json 的区别——前者是业务维度的子应用版本快照,后者是npm 维度的依赖树快照;
  3. 能否在无 Lerna/Rush/Nx 的老旧项目中,用 Grunt 低成本落地版本治理,这是国内很多银行、运营商、政务系统仍在维护的遗留栈。

知识点

  1. Grunt 任务模型grunt.initConfig 注册多任务 → grunt.registerTask 组合任务 → grunt.task.run 顺序/并行执行。
  2. Grunt 文件 APIgrunt.file.readJSON/writeJSON/glob 可以原子读写 JSON,避免额外装 fs-extra
  3. semver 解析:使用 semver 包判断范围交集,国内镜像源可用 npm.taobao.org 加速安装。
  4. 锁文件格式:自研锁文件通常放在 /locks/subapp-lock.json,字段包括 {appName: {version, integrity, repo, branch, commit}},方便后续出包审计
  5. 钩子植入:在 grunt-contrib-watch 里监听各子应用 package.json 变动,增量检查而非全量,降低 300+ 子应用大型系统的 CPU 占用。
  6. 错误码规范:国内 CI 平台(如阿里云效、腾讯蓝盾)依赖进程退出码做质量门禁,因此 Grunt 任务失败必须 grunt.fail.fatal() 并返回非零码。
  7. 并发安全:Grunt 默认单进程,若用 grunt-concurrent 做并行检查,需加 .grunt-lock 文件锁,防止同时写锁文件导致脏数据。

答案

  1. 目录约定

    locks/
    ├─ subapp-lock.json          # 生成的互锁文件
    packages/
    ├─ app-a/package.json
    ├─ app-b/package.json
    ├─ app-c/package.json
    
  2. 安装必备插件

    npm i -D grunt grunt-contrib-watch semver chalk
    
  3. Gruntfile.js 核心实现

    module.exports = function(grunt) {
      grunt.initConfig({
        // 1. 读取所有子应用 package.json
        meta: {
          subApps: grunt.file.expand({cwd: 'packages'}, '*/package.json')
                    .map(f => f.replace('/package.json', ''))
        },
    
        // 2. 版本互锁检查任务
        version_lock: {
          check: function() {
            const semver = require('semver');
            const lockPath = 'locks/subapp-lock.json';
            const oldLock = grunt.file.exists(lockPath) ? grunt.file.readJSON(lockPath) : {};
            const newLock = {};
    
            let mismatch = false;
    
            grunt.config.get('meta.subApps').forEach(name => {
              const pkg = grunt.file.readJSON(`packages/${name}/package.json`);
              const ver = pkg.version;
    
              // 如果锁文件已存在且版本冲突
              if (oldLock[name] && !semver.satisfies(ver, oldLock[name].version)) {
                grunt.log.error(`❌ ${name} 期望 ${oldLock[name].version},实际 ${ver}`);
                mismatch = true;
              }
              newLock[name] = {
                version: ver,
                integrity: require('crypto').createHash('sha256')
                            .update(grunt.file.read(`packages/${name}/package.json`))
                            .digest('hex').slice(0, 12),
                repo: pkg.repository ? pkg.repository.url : '',
                commit: process.env.CI_COMMIT_SHA || 'dev'
              };
            });
    
            if (mismatch) grunt.fail.fatal('版本互锁检查未通过,请统一版本后重试');
    
            // 3. 写锁文件
            grunt.file.write(lockPath, JSON.stringify(newLock, null, 2));
            grunt.log.ok('✅ 子应用版本互锁文件生成完毕');
          }
        },
    
        // 4. 文件监听,增量检查
        watch: {
          pkg: {
            files: ['packages/*/package.json'],
            tasks: ['version_lock:check'],
            options: { event: ['changed'] }
          }
        }
      });
    
      grunt.registerTask('version_lock:check', grunt.config.get('version_lock.check'));
      grunt.registerTask('default', ['version_lock:check']);
    };
    
  4. 接入 CI
    .gitlab-ci.yml 或 蓝盾 YAML 中增加:

    version-lock:
      script: npx grunt version_lock:check
      only:
        - merge_requests
        - master
    

    若任务失败,MR 无法合并,实现强制版本一致

  5. 使用方式

    • 开发阶段:npx grunt watch 本地保存即检查;
    • 集成阶段:npx grunt 在构建机生成锁文件并随制品库归档;
    • 回滚场景:直接用锁文件里的 commit 字段回退到对应子应用版本,无需人工翻仓库

拓展思考

  1. 多分支策略:若子应用并行维护 release/1.xrelease/2.x,可在锁文件中加入 baseBranch 字段,Grunt 任务里用 git rev-parse --abbrev-ref HEAD 区分分支,实现分支级版本隔离
  2. 二进制制品互锁:前端子应用常把构建产物 dist.tgz 上传到 Nexus,锁文件里可增加 tarHash 字段,Grunt 通过 grunt-http 拉取头信息校验,**防止“源码版本一致但制品被覆盖”**的幽灵问题。
  3. 自动修正:在 version_lock 任务里再加 --fix 参数,检测到冲突时直接用 semver.coerce 把子应用版本对齐到最高版本,并回写 package.json,实现“一键升级”;但需提交 MR 走评审,符合国内变更必须有记录的审计要求。
  4. 与 npm workspaces 共存:Grunt 只做业务版本锁,不碰 node_modules 结构,避免与 npm v7+ workspaces 冲突;在 postinstall 钩子中调用 grunt version_lock:check既享受 npm 原生依赖解析,又保留 Grunt 的灵活任务链