解释 cwd、src、dest、ext 在文件模板中的执行顺序

解读

在 Grunt 的 files 数组配置里,每一条映射规则都会经历“路径解析 → 文件匹配 → 目标生成 → 后缀修正”四步流水线。
面试官想确认的是:

  1. 你能否把四元参数(cwd、src、dest、ext)放进同一条时间线;
  2. 你是否理解每一步产生的中间结果,以及后一步如何消费前一步的输出;
  3. 当四元参数同时出现时,哪一步才真正决定“文件落盘位置”和“最终后缀”。

国内项目普遍混合 ES5/TS、图片、字体等多类型资源,一旦顺序理解错误,就会出现“文件找不到”“输出层级多一层”“压缩后后缀仍是 .js”等线上事故,所以该考点在简历面与技术面都会被追问。

知识点

  1. cwd(current working directory)
    仅影响 src 的匹配基准路径,不会拼接到 dest。
  2. src
    支持通配符,匹配结果 = cwd + 通配符展开后的相对路径列表。
  3. dest
    可以是目录或模板字符串;若 dest 以 / 结尾表示目录,否则视为目标文件路径模板。
  4. ext
    在 dest 模板计算完毕后再做字符串替换,仅修改后缀名,不改变目录层级。

官方源码顺序(grunt-contrib-copy / uglify / sass 均复用同一逻辑):
cwd → src → dest → ext

答案

执行顺序严格为 cwd → src → dest → ext,每一步的输入都是上一步的输出:

  1. cwd 生效:Grunt 先把进程工作目录切到 cwd 指定的目录,后续所有 src 通配符都在该目录下展开。
  2. src 展开:根据上一步的基准目录,利用 minimatch 得到“相对 cwd”的文件数组;结果中已去掉 cwd 前缀。
  3. dest 计算:对每条匹配到的文件,把 src 的相对段拼到 dest 模板后面,形成“目标路径草稿”;如果 dest 里出现 {{filename}}{{ext}} 等占位符,也在这一步完成字符串渲染。
  4. ext 重写:扫描第 3 步得到的草稿路径,若配置了 ext,则无条件把最后一个 .xxx 段替换为 ext 指定的新后缀,生成最终的磁盘路径。

示例:

files: [{
  cwd: 'src',
  src: '**/*.coffee',
  dest: 'dist/js/',
  ext: '.js'
}]
  • cwd 把搜索基准定在 src/
  • src 匹配到 src/a/b.coffee → 中间结果 a/b.coffee
  • dest 目录模板拼接 → dist/js/a/b.coffee
  • ext 替换 → 最终输出 dist/js/a/b.js

拓展思考

  1. flatten 与 rename 函数
    若同时出现 flatten: true,则在 dest 计算前先把目录拍平;若提供 rename 函数,则 ext 替换后还会再走一次自定义映射,顺序变为 cwd → src → flatten → dest → ext → rename
  2. 动态映射性能
    国内大型项目 10k+ 文件场景下,cwd 若指到项目根,会导致 minimatch 全树扫描;推荐把 cwd 拆到最小公共子目录,可缩短 30% 构建时间。
  3. 多 ext 规则
    当需要把 .ts.js、同时把 .scss.css 时,不要写两条任务,而是利用 expand: true 配合 extDot: 'last' 让 ext 只替换最后一个点号,避免把 component.d.ts 误改成 component.d.js
  4. 与 webpack/vite 差异
    面试官常追问“为何 Grunt 需要四元组而 webpack 不需要”。核心差异在于 Grunt 是任务拼装,路径变换由用户显式声明;webpack 是依赖图驱动,路径规则内置于 loader。理解这一点,可以顺势把话题引到“如何渐进式从 Grunt 迁移到 rollup/esbuild”上,展示你对国内老项目改造的深度思考。