gulp 文件处理实战:深入理解 src() 与 dest() 的流式文件管道
【免费下载链接】gulpA toolkit to automate & enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp
gulp 的核心是"流式构建":文件从磁盘流入、在管道中被插件转换、再流回磁盘。本文围绕入门指南 Working with Files 展开,系统讲解src()与dest()两个文件入口/出口方法,涵盖基础复制、管道中间插入文件、分阶段输出以及 buffering / streaming / empty 三种工作模式,并结合本仓库源码与测试说明其底层实现。读完本文,你将能写出结构清晰、可复用的 gulp 文件处理任务,并理解每个选项对流的实际影响。
src() 与 dest():文件管道的两端
gulp 通过src()和dest()两个方法与你电脑上的文件系统交互,它们均由 gulp 实例暴露,可直接解构使用(见 index.js 中this.src、this.dest、this.symlink的绑定,实际实现来自vinyl-fs模块)。
src()(读取端):接收一个 glob 模式,从文件系统中定位所有匹配的文件,将其内容读入内存,并产生一个 Node 流)。dest()(写入端):接收一个输出目录字符串,也产生一个 Node 流,通常作为管道的终止流(terminator)。每当有 Vinyl 对象流经它,就会把文件内容及其它细节写入到指定目录。symlink():与dest()行为类似,但创建的是符号链接而非普通文件,详见 symlink() API。
按照 Creating Tasks 的要求,src()产生的流应当从任务函数中返回,以此向 gulp 发出异步完成信号。一个最简单的复制任务如下:
const { src, dest } = require('gulp'); exports.default = function() { return src('src/*.js') .pipe(dest('output/')); }用 .pipe() 串联插件转换文件
流的核心 API 是.pipe()方法,用于把 Transform 或 Writable 流串联起来。绝大多数情况下,插件被放在src()与dest()之间,对流经的文件进行转换:
const { src, dest } = require('gulp'); const babel = require('gulp-babel'); exports.default = function() { return src('src/*.js') .pipe(babel()) .pipe(dest('output/')); }上面的任务读取src/下所有.js文件,经 Babel 转译后写入output/。这种"源 → 插件转换 → 输出"的形态是 gulp 任务的基本骨架;插件只对流经的文件做变换,文件在dest()中落盘。
在管道中间添加文件:src() 不只在开头
src()也可以被放置在管道的中间,根据给定的 glob 向流中追加文件。新增的文件只会对管道后续的变换可见;如果 globs 之间发生重叠,重叠的文件会被再次添加。
这一特性非常适合"先转译一部分文件,再混入普通 JS 文件,最后统一压缩"的场景:
const { src, dest } = require('gulp'); const babel = require('gulp-babel'); const uglify = require('gulp-uglify'); exports.default = function() { return src('src/*.js') .pipe(babel()) .pipe(src('vendor/*.js')) .pipe(uglify()) .pipe(dest('output/')); }此例中,vendor/*.js是第三方库文件,不需要 Babel 转译,因此在babel()之后才被加入流中;它们与转译后的业务代码一起进入uglify()统一压缩。需要注意的是,src()内对重叠 globs 会尽量去重,但跨多个src()调用产生的重复文件不会被去重(参见 Explaining Globs)。
分阶段输出:dest() 写在管道中间
dest()同样可以出现在管道中间,用于把中间状态先落盘。当文件流经中间的dest()时,gulp 会把当前状态写入文件系统,更新 Vinyl 对象的路径以指向新的输出位置,然后该文件继续沿管道向下游流动。
借助这个特性,可以用同一条管道同时产出未压缩版和压缩版文件:
const { src, dest } = require('gulp'); const babel = require('gulp-babel'); const uglify = require('gulp-uglify'); const rename = require('gulp-rename'); exports.default = function() { return src('src/*.js') .pipe(babel()) .pipe(src('vendor/*.js')) .pipe(dest('output/')) .pipe(uglify()) .pipe(rename({ extname: '.min.js' })) .pipe(dest('output/')); }执行流程分两个阶段:
- 文件经
babel()转译后,由第一个dest('output/')写入未压缩版本; - 文件继续流向下游,经
uglify()压缩、rename()改写扩展名为.min.js,再由第二个dest('output/')写入压缩版本。
最终output/目录中同时存在原始 JS 与*.min.js压缩文件,且全程只扫描了一次源文件。这与文档 minified-and-non-minified 描述的"同一管道同时产出压缩与非压缩产物"思路一致。
三种工作模式:buffering、streaming、empty
src()支持三种工作模式,由src()的buffer和read两个选项组合决定:
| 模式 | buffer | read | 行为与适用场景 |
|---|---|---|---|
| Buffering(缓冲,默认) | true(默认) | true(默认) | 将文件内容一次性全部加载进内存,Vinyl 对象的contents为 Buffer。大多数插件都在此模式下工作,许多插件不支持 streaming 模式。 |
| Streaming(流式) | false | true | 文件内容不从磁盘一次性载入,而是以小数据块(chunk)流式传入,Vinyl 对象的contents是一个暂停状态的流。主要面向无法塞进内存的大文件,如超大图片或视频。若需使用此模式,应寻找支持它的插件,或自行编写插件。 |
| Empty(空) | — | false | 完全不读取文件内容,contents为null。适合只处理文件元数据的场景。 |
两种选项都支持函数形式:当传入函数时,gulp 会以每个 Vinyl 对象为参数调用该函数,并以返回值作为该文件的选项值。
模式的实际验证:测试用例
仓库测试 test/src.js 直接验证了这三种模式的行为:
- 默认缓冲模式:读取
./fixtures/*.coffee后,file.contents是一个 Buffer,内容等于Buffer.from('this is a test')(见 test/src.js); read: false:file.contents为null,路径与元数据仍然存在(见 test/src.js);buffer: false:file.contents是一个流,需要订阅其data/end事件才能拼出完整内容(见 test/src.js)。
相应地,test/dest.js 验证了dest()在三种模式下的落盘行为:缓冲模式直接写入 Buffer 内容(test/dest.js)、read: false时不会写出任何文件(test/dest.js)、流式内容同样能正确写入磁盘并重建目录结构(test/dest.js)。
大文件与内存的权衡
选择哪种模式取决于文件体积与插件支持情况。Buffering 模式简单直接、兼容性最好,是默认选择;Streaming 模式避免了"一次读入整个大文件"的内存压力,但要求下游插件能够消费流式内容——这是许多插件不具备的能力,需要在使用前确认;Empty 模式则完全不触碰内容,适合收集文件名、做增量构建、检查目录结构等元数据场景。
src() 与 dest() 的核心选项速查
src() 常用选项
以下选项摘录自 src() API 文档,其中函数型选项都会被逐一调用并传入每个 Vinyl 对象:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
encoding | string / boolean | "utf8" | 设为false时按二进制处理内容;设为字符串时作为文本编码。 |
buffer | boolean / function | true | 控制 buffering / streaming 模式,见上文三种模式。 |
read | boolean / function | true | 为false时不读取文件内容,且对应的 Vinyl 对象无法通过.dest()写盘。 |
since | date / timestamp / function | — | 只创建指定时间之后被修改过的文件的 Vinyl 对象,常用于增量构建。 |
removeBOM | boolean / function | true | UTF-8 文件的 BOM(字节顺序标记)在 UTF-8 中没有实际意义,默认会被移除;设为false则保留。 |
sourcemaps | boolean / function | false | 为true时启用内联 sourcemap 支持,会加载内联 sourcemap 并解析外部 sourcemap 链接。 |
resolveSymlinks | boolean / function | true | 为true时递归解析符号链接到真实目标;为false时保留链接并把原路径写入 Vinyl 对象的symlink属性。 |
allowEmpty | boolean / function | false | 当 glob 只能匹配单个文件(如foo/bar.js)却找不到匹配时,src()会抛出 "File not found with singular glob" 错误;设为true可抑制该错误。 |
ignore | string / array | — | 要排除的 globs,会与取反 globs 合并处理;该列表始终会匹配点文件(dot files)。 |
cwd | string | process.cwd() | 与相对路径拼接成绝对路径的基准目录,绝对路径下忽略该选项。 |
base | string | — | 显式设置 Vinyl 对象的base属性(glob base),影响dest()输出时的目录结构保留方式。 |
dest() 常用选项
以下选项摘录自 dest() API 文档:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cwd | string / function | process.cwd() | 输出路径的拼接基准目录。 |
mode | number / function | Vinyl 对象的stat.mode | 创建文件时使用的权限模式;未设置且缺少stat.mode时使用进程默认模式。 |
dirMode | number / function | — | 创建目录时使用的权限模式,未设置时使用进程默认模式。 |
overwrite | boolean / function | true | 为true时覆盖路径相同的已存在文件。 |
append | boolean / function | false | 为true时把内容追加到文件末尾,而不是替换原有内容。 |
sourcemaps | boolean / string / function | false | 为true时把内联 sourcemap 写入输出文件;传入字符串路径则在该路径写出外部 sourcemap。 |
relativeSymlinks | boolean / function | false | 为false时创建的符号链接是绝对路径(junction 必须是绝对路径,不受此选项影响)。 |
useJunctions | boolean / function | true | 仅 Windows 上有效;为true时目录符号链接以 junction 形式创建。 |
值得注意的错误行为
src()的globs只匹配到单一文件(如foo/bar.js)但未找到时,会抛出File not found with singular glob,可用allowEmpty: true抑制;传入非法 glob 时抛出Invalid glob argument。dest()的directory为空字符串或非字符串/函数时,抛出Invalid dest() folder argument. Please specify a non-empty string or a function.。
源码视角:src / dest 如何接入 gulp
从仓库源码 index.js 可以看到 gulp 的文件能力并非自研,而是委托给vinyl-fs模块:
var vfs = require('vinyl-fs'); // ... Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; Gulp.prototype.symlink = vfs.symlink;对应 package.json 中的依赖"vinyl-fs": "^4.0.2",src()、dest()、symlink()分别是vinyl-fs暴露的 Vinyl 适配器方法——src(globs, [options])返回产出 Vinyl 对象的流,dest(folder, [options])返回消费 Vinyl 对象的流(详见 API Concepts 中的 Vinyl adapters)。gulp 的其它能力同样来自小模块组合:undertaker负责任务注册、glob-watcher负责文件监听、bach负责series()/parallel()编排,这一点在 concepts.md 中有系统说明。
从 package.json 的"version": "5.0.1"与"engines": { "node": ">=10.13.0" }可知,本文所述行为对应 gulp 5.x。若需要验证src()与dest()的实际行为,可直接运行仓库测试:npm test(内部通过 mocha 执行 test/src.js 与 test/dest.js)。
结语
src()与dest()构成了 gulp 文件处理的一进一出:src()用 glob 定位并读取文件、产出 Vinyl 流,dest()把流中的文件写回磁盘;二者都可以放在管道中间实现"流中插文件"与"分阶段落盘"两种高级编排;buffer与read组合出的三种模式让 gulp 既能应对默认的缓冲处理,也能覆盖大文件流式处理和纯元数据场景。理解这些机制,再配合 Globs 语法、插件生态 与 监听文件变化,即可搭建出完整的自动化构建流程。
【免费下载链接】gulpA toolkit to automate & enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考