说到 gulp 的运作方式,就不得不提到 vinyl 和 Node.js 的 stream。
vinyl
vinyl 是 gulp 所使用的虚拟的文件格式,在它的自述文件是这么说的:“当提到文件时你首先想到的是什么?肯定是路径和内容吧”,它主要记录的信息有:
path
:文件路径contents
:文件内容cwd
:程序执行的目录base
:用 glob 寻找文件时开始的目录,例如src/**/*.js
,那base
就会是src
,这可以用来重现目录结构
另外它还有几个函数用来判断这个文件的内容是什么类型的这类操作,到于这个虚拟文件实际上用在什么地方,咱们稍后再说,先创建一个文件试试:
代码语言:javascript复制const { readFile } = require('fs/promises')
const Vinyl = require('vinyl')
const file = new Vinyl({
path: __filename, // 这个文件的路径
// 在 Node.js 14.8.0 中 支持 top level await,不过还是建议用 async function 封装
contents: await readFile(__filename),
cwd: process.cwd(), // 不设定的话默认值为 process.cwd()
base: process.cwd(), // 不设定的话默认值为 process.cwd()
})
console.log(file) // 这是应该能看到 <File "file.js" <Buffer ...>>
这样就创建好了一个 gulp 用的文件格式,接下来是 Node.js 的 stream。
stream
stream 设计的本意是要处理大文件的,它能一次读取文件的一小部分,然后再传给调用者进行处理:
代码语言:javascript复制const { createReadStream } = require('fs')
// 创建一个读取文件的 stream
const stream = createReadStream(__filename)
// 设置文件字符集编码,否则就会以 Buffer (二进制数据) 的格式读取
stream.setEncoding('utf-8')
// 处理数据
stream.on('data', (chunk) => {
console.log('chunk', JSON.stringify(chunk))
})
// 结束
stream.on('end', () => {
console.log('end')
})
实际上它是基于 EventEmitter 之上创建的一组 API,比如 on
就是来自于 EventEmitter,只要照着它的模式,也不一定只能传小块的文件,在 Node.js 中的 stream 也有一个对象模式,如果传的数据不是缓冲区或流就应该设置为对象模式,而对象模式跟一般的模式主要的区别就是不需要处理字符集编码。
再回到 gulp,还记得之前说过 src
是回传一个 stream 吗?接下来看看里面到底传的是什么,先写个 gulpfile 来试看看:
exports.stream = function () {
const stream = src('./src/**.js')
stream.on('data', (data) => {
console.log(data)
})
return stream
}
输出:
代码语言:javascript复制<File "file.js" <Buffer ...>>
没错,这就是 Vinyl 的文件,gulp 用 stream 的对象模式在传输这些文件,plugin 其实上就是回传一个 Transform
的 stream(Node.js 中 stream 的一种,stream 的种类有 Readable、Writeable、Duplex、Transform)来转换这些文件,比下面是一个把文件内容都换成大写的流:
const { Transform } = require('stream')
exports.uppercase = function () {
return src('./src/**.js')
.pipe(
new Transform({
// 设置为 object mode
objectMode: true,
transform(file, _enc, cb) {
// 把文件内容转换成为字符串
const content = file.contents.toString()
// 转为大写后再转回 Buffer 存回去
file.contents = Buffer.from(content.toUpperCase())
// 用 callback 回传
cb(null, file)
},
})
)
.pipe(dest('upper'))
}
现在我们有了一个把文件全转大写的 plugin了,不过没什么实用上的意义。这样转来转去的效率太低了。
剩下的部分就是 gulp 处理任务的注册与依赖性的逻辑了,依赖性主要是由 undertaker
处理的,不过我觉得这里没什么特别的东西,所以有兴趣就自己去看看吧。