iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 28
1
Modern Web

前端建置工具完全手冊系列 第 28

Day 28: gulp 是怎麼運作的

要講到 gulp 怎麼運作的就不得不講到 vinyl 跟 Node.js 的 stream

vinyl

vinyl 是 gulp 用的虛擬的檔案格式,在它的 readme 是這麼說的:「說你檔案你第一個想到的是什麼?路徑跟內容吧」,它主要紀錄的資訊有:

  • path: 檔案路徑
  • contents: 檔案內容
  • cwd: 程式執行的目錄
  • base: 用 glob 找檔案時開始的目錄,比如 src/**/*.js ,那 base 就會是 src ,這可以用來重現資料夾結構

另外它上面還有幾個函式可以用來判斷這個檔案的內容是什麼類型的之類的,至於這個虛擬檔案實際上用在哪,這晚點就知道了,這邊先建一個檔案試試:

const { readFile } = require('fs/promises')
const Vinyl = require('vinyl')

const file = new Vinyl({
  path: __filename, // 這個檔案自己
  // top level await 在 Node.js 14.8.0 有支援,不過還是建議用 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 原本設計是要處理大的檔案的,它能一次讀出一小部份的檔案,然後送給使用者處理:

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 也有個 object mode ,只要傳遞的資料不是 Buffer 或 stream 就應該設定為 object mode ,而 object mode 跟一般的模式主要的差別就是有沒有要處理 encoding

回到 gulp ,還記得之前說過 src 是回傳一個 stream 嗎?我們來看看裡面到底在傳什麼,我們寫個 gulpfile 來試看看:

exports.stream = function () {
  const stream = src('./src/**.js')
  stream.on('data', (data) => {
    console.log(data)
  })
  return stream
}

你有實際執行應該會看到很熟悉的輸出:

<File "file.js" <Buffer ...>>

對,就是 vinyl 的檔案, gulp 用 stream 的 object mode 在傳遞這些檔案, plugin 其實就是回傳一個 Transform 的 stream (Node.js 的一種 stream ,分成 Readable, Writeable, Duplex, Transform ,不過這邊沒打算詳細介紹) 來轉換這些檔案,比如我們這邊弄一個把檔案內容都換成大寫的 stream :

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 了,而且這樣看起來轉換來轉換去的 (Buffer 跟 string),效率真的不高

剩下的部份就是 gulp 處理 task 的註冊與相依性的邏輯了,相依性主要是由 undertaker 處理的,不過我覺得這部份其實什麼特別的東西,所以有興趣就自己去看看吧,下一篇來講 eslint


上一篇
Day 27 介紹 gulp
下一篇
Day 29: eslint
系列文
前端建置工具完全手冊30

尚未有邦友留言

立即登入留言