gulp 5 实战:用 del 模块删除文件与文件夹(构建前清理与管道内删除完整指南)
2026/9/19 6:25:06 网站建设 项目流程

gulp 5 实战:用 del 模块删除文件与文件夹(构建前清理与管道内删除完整指南)

【免费下载链接】gulpA toolkit to automate & enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp

本指南基于 gulp 官方 Recipes 中的 Delete files and folders 一文,结合当前仓库(gulp 5.0.1)的 API 文档与源码,系统讲解在 gulp 构建流程中删除文件与文件夹的正确姿势:为什么删除操作不需要 gulp 插件、如何用原生 Node 模块del配合 glob 模式实现构建前清理、以及如何在管道(pipeline)中借助vinyl-paths边处理边删除。读完你将掌握两套可复制的清理任务模板,并理解其底层原理与性能取舍。

为什么删除文件不需要 gulp 插件

gulp 的插件生态围绕文件内容构建:插件接收 Vinyl 文件流,对contents(缓冲区或流)做转换,再交给下游。而删除文件与内容无关——它操作的是文件系统的目录项,既不读取内容,也不产生输出。因此在 gulp 生态里,删除文件的标准做法不是找插件,而是直接调用一个普通的 Node 模块。

官方 Recipe 明确指出:

由于删除文件不作用于文件内容,没有理由使用 gulp 插件——这是一个使用原生 Node 模块的绝佳场景。

官方推荐的模块是del,因为它天然支持:

  • 一次删除多个文件(接收文件路径/模式数组);
  • glob 通配(基于 multimatch 的 globbing 语法),可以直接用模式而非穷举路径;
  • 负向模式!前缀),实现"删除某目录内除个别文件外的一切"这类常见需求。

del返回 Promise,而 gulp 任务只要返回 Promise(或 stream、callback 等)即可被正确追踪完成,详见 异步完成。这意味着del(...)可以直接作为任务的返回值,无需任何包装。

安装依赖

--save-dev安装 gulp 与 del:

$ npm install --save-dev gulp del

如果还要在管道内删除,再额外安装vinyl-paths

$ npm install --save-dev gulp del vinyl-paths

vinyl-paths的作用是提取流中每个文件的路径并暴露给外部函数,后面会详细演示。需要说明的是,当前仓库(gulp 5.0.1,见 package.json)本身并不依赖delvinyl-paths——这正是该方案的设计意图:删除是普通 Node 能力,无需内建于 gulp 核心。

场景一:构建前清理(clean 任务)

最常见的需求:构建开始前,把上次的产物目录清理干净。假设项目文件结构如下:

. ├── dist │ ├── report.csv │ ├── desktop │ └── mobile │ ├── app.js │ ├── deploy.json │ └── index.html └── src

我们希望每次构建前清空mobile目录的内容(保留目录本身),同时保留deploy.json不被删除:

var gulp = require('gulp'); var del = require('del'); gulp.task('clean:mobile', function () { return del([ 'dist/report.csv', // 这里使用 glob 模式匹配 mobile 目录内的所有内容 'dist/mobile/**/*', // 我们不希望清理这个文件,因此对模式取反 '!dist/mobile/deploy.json' ]); }); gulp.task('default', gulp.series('clean:mobile'));

逐行解析这段模式数组

  • 'dist/report.csv':精确指定单个文件,直接删除;
  • 'dist/mobile/**/*'**匹配任意层级的目录、*匹配单层内的任意字符(含零个),组合起来即"递归匹配mobile目录下的一切内容"。这与 gulp 中**的语义一致,详见 Explaining Globs;
  • '!dist/mobile/deploy.json':负向(否定)模式。del 基于 multimatch 处理模式集合:先由正向模式圈定匹配集合,再用负向模式从中剔除,最终效果是"除了deploy.jsonmobile下所有文件都被删除"。

注意,'dist/mobile/**/*'只清理目录内容mobile目录本身保留;如果希望连同目录一起删掉,可以直接写成'dist/mobile'(del 对文件与目录都有效),代价是构建时需要重新创建该目录。二者取舍取决于你的构建脚本是否会重新 mkdir。

任务编排:为什么用 gulp.series

示例中default任务通过gulp.series('clean:mobile')组合。series()会将多个任务按顺序依次执行,前一个完成后才启动后一个,任何任务出错则中止后续任务,详见 series()。把清理任务放在default的最前面,即可保证"先清后建"的顺序:

exports.default = gulp.series('clean:mobile', build);

现代写法:优先使用导出而非 gulp.task

Recipe 使用了gulp.task(...)注册任务——这是 gulp 一直保留的旧式 API。当前仓库(gulp 5)的官方推荐是以命名函数 + 导出作为任务注册机制(见 Creating Tasks),仅在导出不可用的边界场景才回退到task()。对应上面例子的现代写法:

const { series } = require('gulp'); const del = require('del'); function cleanMobile() { return del([ 'dist/report.csv', 'dist/mobile/**/*', '!dist/mobile/deploy.json' ]); } exports.cleanMobile = cleanMobile; // 公开任务:gulp cleanMobile exports.default = series(cleanMobile); // 默认任务:gulp

两种写法都成立;导出方式更符合 gulp 5 的惯例,也避免了字符串引用(前向引用)带来的运行时开销,详见 series() 中关于 forward references 的说明。

glob 模式补充:负向模式与顺序

删除任务的核心是写对 glob 模式。官方 Explaining Globs 文档给出了与本例直接相关的两个要点:

  1. !负向模式:以!开头的 glob 会"否定"该模式,将其匹配结果从集合中完全剔除。在 gulp 5 中,所有负向 glob 会作用于每一个正向 glob(这是相对 v5 之前版本的行为变更)。删除任务中同样建议用负向模式来"白名单化":
    ['dist/mobile/**/*', '!dist/mobile/deploy.json']
  2. **双星号的收敛使用**会跨任意层级匹配,务必用目录前缀收敛范围,否则可能误匹配node_modules等无关目录。例如dist/mobile/**/*就被限制在dist/mobile/之下。

删除目录时不必像src()那样担心"必须至少匹配一个文件"的报错——src()在 glob 无匹配时会抛出 "File not found with singular glob"(可通过allowEmpty: true抑制,见 src() 选项),而del对不存在的路径默认是宽容的,这正是清理任务健壮性的来源之一。

场景二:在管道(pipeline)中删除文件

有时需求是:把源文件从临时目录移动/整理到输出目录,源文件随管道流动被消费后即刻删除。官方方案是vinyl-paths——它把流中每个 Vinyl 文件的path收集成数组,传给你提供的函数(这里是del),并等待其完成后再把文件继续向下游传递。

假设文件结构:

. ├── tmp │ ├── rainbow.js │ └── unicorn.js └── dist
var gulp = require('gulp'); var stripDebug = require('gulp-strip-debug'); // 仅作为示例插件 var del = require('del'); var vinylPaths = require('vinyl-paths'); gulp.task('clean:tmp', function () { return gulp.src('tmp/*') .pipe(vinylPaths(del)) .pipe(stripDebug()) .pipe(gulp.dest('dist')); }); gulp.task('default', gulp.series('clean:tmp'));

工作流程拆解

  1. gulp.src('tmp/*')tmp/下的文件读入流(Vinyl 对象);
  2. .pipe(vinylPaths(del))vinylPaths把流经的rainbow.jsunicorn.js的路径组成['tmp/rainbow.js', 'tmp/unicorn.js']传给deldel删除这些文件并返回 Promise,vinylPaths等待删除完成后才放行文件;
  3. .pipe(stripDebug())对文件内容做示例性转换(剥离 debug 语句);
  4. .pipe(gulp.dest('dist'))把处理后的文件写入dist

正如 Recipe 所注明的,这条任务的净效果是只清理tmp目录中的文件(源文件在管道中途即被删除),不会触碰dist里的任何输出。

一个必须留意的风险

管道内删除意味着"边读边删"。如果srcdest指向同一目录,下游还没来得及写出的文件可能已被删除,导致构建损坏。所以管道内删除务必保证:

  • 源目录与目标目录严格分离(如tmpdist);
  • 删除发生在需要该文件内容的处理步骤之前,且该文件后续只被写出、不再被读取。

何时不该在管道里删除

Recipe 在末尾给出了关键的性能告诫:

只有在管道里已经使用了其他插件时,才这样做;否则直接使用模块(del)即可,因为gulp.src的成本很高。

原因在于gulp.src并非免费的:它基于vinyl-fs构建流(见 入口文件,Gulp.prototype.src = vfs.src),内部要做 glob 展开、文件系统遍历、文件读取(缓冲到内存或转为流)等操作。如果只是为了"删除"而额外架设一条src() → vinylPaths(del)管道,属于高成本低收益。删除本身不需要读取文件内容,直接del(patterns)一行就能完成,不必经过流。

因此决策标准很简单:

  • 只是清理目录 → 直接调用del(patterns)
  • 删除只是某个已有管道(含src、多个插件、dest)的附带环节 → 才用vinylPaths(del)挂在管道里。

从源码看:gulp 的任务系统如何承接清理任务

看当前仓库的 入口文件 可以印证上述用法背后的机制:

  • Gulp类继承自undertaker(任务注册与编排系统),并在构造函数中绑定了taskseriesparallelregistrytreelastRun等方法——这就是gulp.task('clean:mobile', ...)gulp.series(...)可用的来源;
  • srcdestsymlink三个方法直接来自vinyl-fs(一个面向本地文件系统的 Vinyl 适配器),gulp.src因此天然支持 glob、负向模式、allowEmpty等选项(详见 src() 与 API Concepts)。

从实现事实看,gulp 核心并未内置任何"删除"能力——删除从来就不属于流式文件处理(Vinyl 描述的是文件及其内容,见 API Concepts 的 Vinyl 一节)。这正是 Recipe 选择del+vinyl-paths组合的根本原因:职责单一、无需插件、零内容读取开销。

完整可运行模板

把两个场景整合为一个 gulpfile(现代导出风格),可直接复制使用:

const { series } = require('gulp'); const del = require('del'); const vinylPaths = require('vinyl-paths'); const stripDebug = require('gulp-strip-debug'); // 仅示例,按需替换 // 场景一:构建前清理——保留 mobile 目录,但清空其内容并保留 deploy.json function cleanMobile() { return del([ 'dist/report.csv', 'dist/mobile/**/*', '!dist/mobile/deploy.json' ]); } // 场景二:管道内删除——从 tmp 读入、删除源文件、转换后写入 dist function processTmp() { return gulp.src('tmp/*') .pipe(vinylPaths(del)) .pipe(stripDebug()) .pipe(gulp.dest('dist')); } exports.clean = cleanMobile; exports.process = processTmp; exports.default = series(cleanMobile, processTmp);

小结与最佳实践

要点结论
删除工具原生 Node 模块del,不需要 gulp 插件
批量模式del([...patterns]),支持**/*!负向模式
构建前清理返回del(...)(Promise)即可,配合series保证先清后建
管道内删除已有多插件管道时用vinylPaths(del),否则直接用del
性能gulp.src代价高,仅为删除而建管道不划算
安全管道内删除要保证 src/dest 目录分离,避免删掉未写出的文件

相关文档可继续深入阅读:官方 Recipe 原文 Delete files and folders、Explaining Globs、创建任务、series() 与 src()。

【免费下载链接】gulpA toolkit to automate & enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询