要說到 gulp 的運做方式,就不得不提到 vinyl 和 Node.js 的 streamjavascript
vinyl 是 gulp 用的虛擬的檔案格式,在它的 readme 是這麼說的:「說你檔案你第一個想到的是什麼?路徑跟內容吧」,它主要紀錄的資訊有:前端
vinyl 是 gulp 所使用的虛擬的文件格式,在它的自述文件是這麼說的:「當提到文件時你首先想到的是什麼?確定是路徑和內容吧」,它主要記錄的信息有:java
path
:文件路徑contents
:文件內容cwd
:程序執行的目錄base
:用 glob 尋找文件時開始的目錄,例如 src/**/*.js
,那 base
就會是 src
,這能夠用來重現目錄結構另外它還有幾個函數用來判斷這個文件的內容是什麼類型的這類操做,到於這個虛擬文件實際上用在什麼地方,我們稍後再說,先建立一個文件試試:git
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 設計的本意是要處理大文件的,它能一次讀取文件的一小部分,而後再傳給調用者進行處理:github
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 來試看看:gulp
exports.stream = function () { const stream = src('./src/**.js') stream.on('data', (data) => { console.log(data) }) return stream }
輸出:segmentfault
<File "file.js" <Buffer ...>>
沒錯,這就是 Vinyl 的文件,gulp 用 stream 的對象模式在傳輸這些文件,plugin 其實上就是回傳一個 Transform
的 stream(Node.js 中 stream 的一種,stream 的種類有 Readable、Writeable、Duplex、Transform)來轉換這些文件,比下面是一個把文件內容都換成大寫的流:promise
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
處理的,不過我以爲這裏沒什麼特別的東西,因此有興趣就本身去看看吧。