如何排除 node_modules 与 .git 目录减少 CPU 占用

解读

在国内前端面试中,这道题表面问“目录排除”,实则考察候选人是否理解 Grunt 的文件匹配性能模型操作系统文件监听开销
node_modules 动辄十万级小文件,.git 对象库同样庞大,若 Grunt 任务(如 watch、copy、eslint、imagemin)默认 glob 到这些目录,会触发巨量 stat 调用,导致 CPU 飙高、Mac/Windows 风扇狂转,甚至 CI 机器卡死。
面试官希望听到:

  1. 精准缩小 glob 范围,让 Grunt 少做无用功;
  2. 利用操作系统级忽略,减少底层监听句柄;
  3. 兼顾团队协作,保证新成员拉代码即可生效,无需额外文档。

知识点

  1. Gruntfile 的 files 数组支持 glob 负向匹配!**/node_modules/**!**/.git/** 写在数组靠后位置即可“排除”。
  2. grunt-contrib-watch 的 options.files 与 options.cwd 组合:设置 cwd: 'src' 可把监听根目录直接限定在业务代码,node_modules 自然不可见。
  3. grunt.file.expandMapping 的 filter 函数:可编程剔除目录,灵活性高于纯 glob。
  4. 操作系统级忽略
    • chokidar(watch 底层)读取 .gitignore 规则,若把 node_modules、.git 写进去,可完全阻止监听句柄创建
    • Windows 下可搭配 grunt-watch-nuclearnode 插件,使用 Windows.ReadDirectoryChangesW 的过滤标志位,降低内核事件量。
  5. npm script 前置清理:在 prewatch 脚本里执行 find . -name node_modules -prune -o -type f -print | head -1 快速探测,若发现仍被扫描,立即报错终止,防止“带病上线”。
  6. CI 场景:国内阿里云、腾讯云 2C4G 机型对文件监听尤其敏感,必须在 .grunt-tasks-cache 里固化上述配置,避免每次构建重复计算。

答案

“我采用三层策略,确保 node_modules 与 .git 对 CPU 零干扰:
第一步,在 Gruntfile 的 glob 规则里显式排除

files: [
  'src/**/*',
  '!**/node_modules/**',
  '!**/.git/**'
]

负向匹配放在数组尾部,保证先选后踢,性能最优。

第二步,grunt-contrib-watch 加双保险

watch: {
  scripts: {
    files: ['src/**/*.js'],
    options: {
      cwd: 'src',          // 监听根目录直接锁在 src
      ignored: [
        '**/node_modules/**',
        '**/.git/**',
        /(^|[\/\\])\../    // 同时忽略所有隐藏文件
      ],
      usePolling: false,   // 国内 Windows 机器关闭轮询,改用原生事件
      interval: 300        // 即使退避,也保持 300 ms 间隔,防止密集触发
    }
  }
}

第三步,把忽略规则下沉到 .gitignore,让 chokidar 直接跳过:

node_modules/
.git/
*.log

这样无论谁拉代码,无需额外配置,即可在开发、构建、CI 三阶段保持 CPU 占用低于 5%。
上线半年,我们团队在 2015 款 MacBook Air 上同时开 3 个并行 watch 任务,温度稳定在 60 ℃ 以内,风扇几乎无声。”

拓展思考

  1. 大仓场景:国内很多公司采用 Lerna + Yarn workspace,node_modules 深度嵌套,可结合 grunt-lerna-exclude 插件,在任务启动前动态解析 lerna.json,把每个 package 的 node_modules 一并写入 ignored 数组,避免手写冗长规则。
  2. Docker 构建:镜像分层缓存时,若 Grunt 仍扫描 node_modules,会击穿缓存。可在 Dockerfile 里先把 .gruntignore 复制进去,再执行 npm ci --only=production,让 Grunt 在构建阶段就感知不到开发依赖目录。
  3. 微前端集成:qiankun 子应用独立构建,主应用通过 grunt-concurrent 并行调用子任务,需在顶层 Gruntfile 统一配置 grunt.option('base', subAppPath),防止子任务回退到根目录再次扫到 node_modules。
  4. 性能度量:使用 node --prof 生成 tick 文件,通过 perf-linux 工具查看内核态 stat 占用,若仍高于 3%,可进一步把 useFsEvents: false 写入 watch 配置,强制走 kqueue,代价是 Big Sur 以上系统需额外签名,适合对性能极致苛刻的 toB 交付场景。