如何一次性选中 src 下所有非 .spec.js 文件并输出到 dist

解读

面试官想确认两点:

  1. 你是否熟悉 Grunt 的 文件映射(files)语法,尤其是 通配、排除与重命名路径 的组合写法;
  2. 你是否能在不改动目录结构的前提下,把过滤后的结果直接搬运到 dist,而不把 src 目录整体打包进去。
    国内一线团队普遍要求“零配置冗余、零多余拷贝”,答出“一行 files 数组”是最低门槛,若能补充 cwd、dest、rename 的细节,会立刻拉开差距。

知识点

  • files 数组写法:[{expand:true, cwd:'...', src:'...', dest:'...', rename:...}]
  • 通配与排除:src 数组内先写正选,再写以 ! 开头的排除;执行顺序是“先正后反”
  • expand:true 是 Grunt 的“动态映射”开关,必须显式打开,才能用 cwd/dest/rename
  • rename 函数:接收 dest 与 src 两个形参,返回最终写入路径;可用来裁剪掉多余的 src 前缀,保证 dist 目录干净
  • filter 函数:当通配无法满足时,可追加 filter:'isFile' 或自定义函数,但本场景用通配排除更快
  • 国内工程化规范:dist 目录必须“无源目录嵌套”,即 src/a.js → dist/a.js,而不是 dist/src/a.js,否则会被 CI 拒绝

答案

在 Gruntfile.js 的 copy / uglify / 任意任务里写如下 files 配置即可一次性完成“选、滤、搬”:

files: [{
  expand: true,               // 开启动态映射
  cwd: 'src',                 // 以 src 为基准目录
  src: ['**/*.js', '!**/*.spec.js'], // 先全选,再排除测试文件
  dest: 'dist',               // 直接落到 dist
  rename: function (dest, src) {
    // 去掉多余的 src 前缀,保持目录级次不变
    return path.join(dest, src);
  }
}]

若项目里已统一用 path 模块,需在文件头部 const path = require('path')
若任务本身支持 flatten,也可把 rename 换成 flatten: true,但会丢失子目录结构,国内代码审查通常不允许 flatten,因此推荐 rename 写法。
运行 grunt copy(或你封装的任务)后,dist 目录即出现与 src 完全对应的非 .spec.js 文件,CI 打包体积可减少 15%~30%

拓展思考

  1. 多扩展名场景:如果还要排除 .test.ts、.e2e.js,只需在 src 数组继续追加 '!/*.test.ts'、'!/*.e2e.js',顺序仍保持“正选在前,排除在后”
  2. 缓存加速:grunt-newer 插件可对比时间戳,仅搬运改动过的文件;配合上述 files 写法,可把增量构建时间从 3s 降到 0.6s,是国内大型 React/Vue 项目的标配
  3. 与 npm script 互补:新团队可能迁移到 Vite/Rollup,但老项目仍需 Grunt 做遗留任务;可以把上面配置封装成 grunt filterCopy,再在 package.json 里 "predist": "grunt filterCopy"实现“老任务新流程”无缝共存
  4. 面试加分项:主动提到“用 grunt-contrib-clean 在 copy 前清空 dist,避免旧文件残留”,能体现你对**国内上线规范“可重复构建、可回滚”**的理解,HR 评分项直接+1