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


<<:  元件类别库魔术

>>:  [Vue.js + Axios] ToDoList (上)

第十六天:复数专案架构

之前在看别人的 JVM 专案时,有时会发现数个不同的 Module 原始码却都指向同一个 Repos...

DAY20 搞样式--CSS Gird 怎麽用(下)?

前言 昨天那篇我已经补上罗!!!此篇将延续前一篇的使用教学,做一些细节延伸的,像是缩写之类的,让我们...

Splunk-SPL

index=mft_log sourcetype="mft:xferlog" ...

Day29 - GitLab CI 如何让工作流程流水线跑快一点?之三 让 Runner 执行更快一点

上一篇谈到从 .gitlab-ci.yml 开始建立关卡及工作,而後依序分派到工作伫列,等待 Git...

Day15-Redux 篇-实作范例

在今天的文章中,我们将会使用 Redux 去完成一个计数器的范例程序。 第一步 使用 createS...