在 Gulp 中按目录批量执行任务:生成每个文件夹一个文件的实战方案 📅 发布时间:2026/9/19 3:23:09 👁 浏览次数: 在 Gulp 中按目录批量执行任务生成每个文件夹一个文件的实战方案【免费下载链接】gulpA toolkit to automate enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp本文基于 Gulp 仓库官方文档 running-task-steps-per-folder.md 编写并结合作品仓库源码与 API 文档进行深度扩充。导读当项目源码按照scripts/jquery/*.js、scripts/angularjs/*.js这样的目录结构组织时我们往往希望对每个子目录分别执行「合并 → 压缩 → 重命名」等一系列构建步骤最终为每个文件夹产出一个独立文件如jquery.min.js、angularjs.min.js。Gulp 的官方 Recipes 文档 Generating a file per folder 提供了一套经典的解决方案通过 Node 文件系统 API 枚举目录、为每个文件夹动态构建一条 stream 管道、再用merge-stream把多条管道合并成一条任务流。读完本文你将掌握如何在 Gulp 5 中实现「按文件夹分组执行任务」的核心套路并理解其底层的 stream 合并原理与常见的任务异步完成机制。一、需求场景为什么需要每个文件夹一个任务在许多真实项目中脚本源码并不是单一入口而是按模块或框架分目录存放/scripts /scripts/jquery/*.js /scripts/angularjs/*.js如果直接用gulp.src(/scripts/**/*.js)把所有文件一股脑合并会得到一个混合了 jQuery 与 AngularJS 代码的大杂烩文件而如果为每个目录手写一条 task又会在目录数量增多时变得难以维护。因此我们期望的产物结构是——每个子目录对应一个独立构建产物/scripts /scripts/jquery.min.js /scripts/angularjs.min.js这正是官方文档 running-task-steps-per-folder.md 所要解决的问题用代码动态枚举文件夹为每个文件夹生成一条独立的 Gulp 管道最后把所有管道的流合并为一条任务流返回。这样无论目录有多少任务逻辑都只需维护一份。二、核心思路拆解官方方案的实现由三个关键环节组成枚举文件夹使用 Node 内置的fs.readdirSync读取目录列表再用fs.statSync(...).isDirectory()过滤出其中的子目录逐目录构建管道对每个文件夹名用gulp.src()读取该目录下的**/*.js文件依次经过gulp-concat、gulp.dest、gulp-uglify、gulp-rename、gulp.dest处理合并多路流将每个文件夹产生的 stream 与根目录文件的构建流一起交给merge-stream合并作为任务返回值。其本质是以目录为单位拆分构建逻辑再以流为单位合并任务结果——这正是 Gulp 流式构建系统the streaming build system见 package.json的核心思想一切皆 stream任务通过返回 stream 来声明异步完成。三、完整示例代码官方文档原版以下是官方文档 running-task-steps-per-folder.md 给出的完整实现var fs require(fs); var path require(path); var merge require(merge-stream); var gulp require(gulp); var concat require(gulp-concat); var rename require(gulp-rename); var uglify require(gulp-uglify); var scriptsPath src/scripts; function getFolders(dir) { return fs.readdirSync(dir) .filter(function(file) { return fs.statSync(path.join(dir, file)).isDirectory(); }); } gulp.task(scripts, function(done) { var folders getFolders(scriptsPath); if (folders.length 0) return done(); // nothing to do! var tasks folders.map(function(folder) { return gulp.src(path.join(scriptsPath, folder, /**/*.js)) // concat into foldername.js .pipe(concat(folder .js)) // write to output .pipe(gulp.dest(scriptsPath)) // minify .pipe(uglify()) // rename to folder.min.js .pipe(rename(folder .min.js)) // write to output again .pipe(gulp.dest(scriptsPath)); }); // process all remaining files in scriptsPath root into main.js and main.min.js files var root gulp.src(path.join(scriptsPath, /*.js)) .pipe(concat(main.js)) .pipe(gulp.dest(scriptsPath)) .pipe(uglify()) .pipe(rename(main.min.js)) .pipe(gulp.dest(scriptsPath)); return merge(tasks, root); });官方文档对这段代码给出的两条关键说明我们在此展开folders.map对每个文件夹执行一次管道构建函数并返回该文件夹的异步 stream。map本身是同步执行、立即构建全部管道的只是各管道的流式处理是异步进行的merge把tasks数组中的所有 stream 与root流合并成一条流只有当所有被合并的流都发出end事件时这条合并流才会结束——从而保证 Gulp 知道整个任务何时真正完成。管道内每一步在做什么处理步骤使用的插件/API作用gulp.src(...)gulp.src()底层为 vinyl-fs读取scriptsPath/folder/**/*.js匹配到的全部文件生成 Vinyl 对象流concat(folder .js)gulp-concat把该文件夹下所有 JS 文件按顺序拼接为folder.jsgulp.dest(scriptsPath)gulp.dest()把未压缩的folder.js写入src/scripts目录uglify()gulp-uglify对合并结果做压缩混淆rename(folder .min.js)gulp-rename把文件名改为folder.min.jsgulp.dest(scriptsPath)gulp.dest()把压缩后的folder.min.js再写入同一目录由于每个文件夹的管道各自独立jquery与angularjs的合并互不干扰各自输出jquery.js/jquery.min.js与angularjs.js/angularjs.min.js。四、从源码看 Gulp 的实现原理4.1gulp.src与gulp.dest的真实面貌在仓库 index.js 中可以看到Gulp类继承自任务注册系统undertaker并把流式文件操作委托给vinyl-fsGulp.prototype.src vfs.src; Gulp.prototype.dest vfs.dest; Gulp.prototype.symlink vfs.symlink;也就是说gulp.src()本质上就是vinyl-fs的src()——它根据 glob 模式从文件系统读取 Vinyl 虚拟文件对象并产生流gulp.dest()则是vinyl-fs的dest()——把流经它的 Vinyl 对象写入磁盘。这两个 API 的完整参数说明可以在 docs/api/src.md 与 docs/api/dest.md 中查到。关于 Vinyl 的base概念src()创建的 Vinyl 对象会把 glob 的glob base如src/scripts设为base属性写入dest()时该base会从输出路径中剥离以保留目录结构详见 docs/api/concepts.md。这正是本例中每个文件夹的流能精确控制输出位置的基础。4.2 返回 stream 就是声明任务完成官方示例中任务函数最后return merge(tasks, root)——把合并流作为任务返回值。在 Gulp 中任务可以是返回 stream、promise、event emitter、child process 或 observable 的异步函数也可以使用 error-first callbackdone来声明完成详见 docs/getting-started/4-async-completion.md。当任务返回 stream 时stream 的成功或错误会告知 Gulp 继续还是结束整个任务链。因此示例中的if (folders.length 0) return done();一行非常关键当目录下没有任何子文件夹时tasks数组为空如果不调用done()而直接return merge([], root)合并流会立即结束、行为可能不符合预期显式调用done()则让无事可做这一情况得到正确处理。五、进阶将回调风格改写为导出风格Gulp 4/5 推荐写法官方文档示例使用gulp.task(scripts, fn)的旧式注册 API。Gulp 4/5 更推荐导出任务函数的方式详见 docs/getting-started/3-creating-tasks.md即把任务函数直接exports出去让gulp命令能够运行它。同样的逻辑改写如下use strict; const fs require(fs); const path require(path); const merge require(merge-stream); const { src, dest, series } require(gulp); const concat require(gulp-concat); const rename require(gulp-rename); const uglify require(gulp-uglify); const scriptsPath src/scripts; function getFolders(dir) { return fs.readdirSync(dir) .filter((file) fs.statSync(path.join(dir, file)).isDirectory()); } function scripts(done) { const folders getFolders(scriptsPath); if (folders.length 0) return done(); // nothing to do! const tasks folders.map((folder) { return src(path.join(scriptsPath, folder, /**/*.js)) .pipe(concat(folder .js)) .pipe(dest(scriptsPath)) .pipe(uglify()) .pipe(rename(folder .min.js)) .pipe(dest(scriptsPath)); }); const root src(path.join(scriptsPath, /*.js)) .pipe(concat(main.js)) .pipe(dest(scriptsPath)) .pipe(uglify()) .pipe(rename(main.min.js)) .pipe(dest(scriptsPath)); return merge(tasks, root); } exports.scripts scripts; exports.default scripts;几点改写说明用const { src, dest } require(gulp)解构替代gulp.src/gulp.dest这是当前仓库 index.js 中Gulp实例为支持解构而显式bind各方法的用途所在gulp.task改为导出函数符合 docs/getting-started/3-creating-tasks.md 中导出即公开任务的推荐模式仍保留 error-first callback 参数done用于处理空目录边界情况。六、相关的官方 Recipes仓库 docs/recipes/README.md 收录了大量可直接复用的实战配方。与本文场景最相关的还有Output both a minified and non-minified version针对单个文件同时输出压缩与未压缩两个版本与本文先dest再压缩再dest的双写手法一致可互为参考Using multiple sources in one task讲解如何在一条任务中使用多个src()数据源与本文合并多路流的思路互补Combining streams to handle errors进一步探讨多条流合并时的错误处理策略。七、常见问题与注意事项依赖安装示例依赖merge-stream、gulp-concat、gulp-rename、gulp-uglify四个第三方包运行前请确认已在package.json中安装npm install --save-dev merge-stream gulp-concat gulp-rename gulp-uglifybase路径影响gulp.src(path.join(scriptsPath, folder, /**/*.js))的 glob base 是src/scripts/folder/配合dest(scriptsPath)会先剥离 base 再写入从而保证合并产物直接落在src/scripts/下空目录保护务必保留if (folders.length 0) return done();这类空集合保护逻辑避免无事可做时任务行为异常压缩插件版本gulp-uglify只接受 ES5 语法若源码使用了 ES6 语法需要先在管道中加入 Babel 转译步骤否则压缩会报错。结语每个文件夹生成一个文件是 Gulp 流式构建中最具代表性的组合模式之一它用fs枚举 map批量建流 merge-stream合并收束三个简单手法解决了目录级重复构建逻辑的抽象问题。理解这套模式也就理解了 Gulp 任务即流的本质——无论构建逻辑被拆分成多少路流最终都通过返回一条合并流告诉 Gulp 任务何时完成。更多实战配方可继续翻阅仓库的 docs/recipes 目录。【免费下载链接】gulpA toolkit to automate enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考