Day 27 介绍 gulp

插播一下, webpack 5 出来了

gulp 是个老牌的 task runner ,它就只是执行设定好的工作,设定写起来其实不难,不过现在前端大家主要还是用 bundler ,所以像 gulp 这样的 task runner 就用的比较少了,事实上还有一个更老的 grunt ,不过这边不会介绍

安装

gulp 本身只有一个套件:

$ yarn add --dev gulp

不过因为它本身并没有做什麽事,通常需要搭配 plugin 来使用,等下会用到 delgulp-babel,同时这边也先把 babel 安装起来:

$ yarn add --dev del gulp-babel @babel/core @babel/preset-env

然後设定 babel ,这边就不赘述了

gulpfile.js

要 gulp 做什麽都要写在 gulpfile.js 这个档案中 (或用 Gulpfile.js),没有这个档案(同时你也没指定其它代替的档案), gulp 根本什麽事也都不会做, gulpfile.js 基本格式很简单:

const { series } = require('gulp')

// 这样就会有一个 task 叫 cleanup
// 你也可以选择不 export ,这样就没办法直接用指令呼叫
exports.cleanup = async function cleanup () {}

// 另一个 task 叫 build
exports.build = async function build () {}

// default 是不指定 task 时预设执行的,这边是依序呼叫 cleanup 跟 build
exports.default = series(cleanup, build)

gulp 的 task 一定是 async 的 (这边不是指一定要是 async function),所以一定要用某种方式回报自己完成,可以用 callback 也可以用 Promise ,另外还有 stream 可以用

const { src, dest } = require('gulp')
// 用 callback
exports.callback = function (cb) {
  cb()
}

// 用 Promise
exports.promise = function () {
  return Promise.resolve()
}

// 用 async function
exports.asyncFunc = async function () {}

// 用 async function
exports.asyncFunc = async function () {}

// 用 stream ,这通常是在用 gulp 的 plugin 时
exports.stream = function () {
  return src('./src/**/*.js').pipe(dest('lib'))
}

另外其实还有 Observable 跟 EventEmitter 可以用,不过通常 stream 跟 async function (promise) 就很够用了,有需要可以自己去看

stream

Node.js 中有个用来处理大档案用的叫 stream 的东西,它会一次只读入一部份的档案,用这种方式来处理大档案,一来是因为 Node.js 可用的 heap 空间一般是有限制的,因此不一定一次能读入很大的档案,二来是把档案一次读入记忆体,然後在记忆体中一次处理完有点那麽违反了 Node.js 的 event driven 的架构,如果没有刻意的把主执行绪转交出去的话,在档案处理完前其它的工作都会被搁置,而 gulp 则同样的使用了 Node.js 的 stream ,在里面传递的不是小块小块的档案内容,而是要处理的档案的资讯与它的内容,这麽说可能不太好懂,下一篇会再来详细解释

在上面你应该也有看到,我用到了 gulp 的两个 API , srcdest

  • src: 建立输入档案的 stream ,里面是放单一个 glob 字串,或是 glob 字串的 array
  • dest: 建立输出的目标,参数一定要是一个资料夹名称

一般使用 gulp 都是使用 src 来指定要处理的档案有哪些,再用 dest 来指定处理完的档案要放哪,如果中间都不处理的话,实际上就跟复制档案没两样:

// 对,就是上面的那个范例,这相当於把 src 下的 js 都照着原本资料夹结构复制到 lib
exports.copy = function () {
  return src('./src/**/*.js').pipe(dest('lib'))
}

glob

glob 是一种一般而言是描述要选择哪些档案的东西,你可能有听过「万用字元」这种称呼,不过现在的 glob 包含的语法要来的更多,如果你知道怎麽用 glob 或是知道怎麽写 gitignore (gitignore 就是 glob 的语法) ,你可以跳过这段,如果不清楚的可以看看,毕竟很多东西都支援这样的语法,像正规表示法一样

主要的几个符号:

  • *: 0 ~ 多个字元,不包含 /
  • **: 同样是 0 ~ 多个字元,但包含 / 这个分隔资料夹的符号,另外它不能在档名的一部份 (src/**.js 是不行的),可以用来代表资料夹下包含子资料夹的档案
  • !: 只能放在开头,代表符合这个条件的不要选择

所以底下的几个范例是:

  • *.js: 这层资料夹下副档名为 js 的档案
  • src/*.js: src 下副档名是 js 的档案
  • src/**: src 下包含子资料夹的档案
  • src/**/*: src 下包含子资料夹的档案
  • src/**/*.js: src 下包含子资料夹的 js 档
  • !src/**/*.js: 不要 src 下包含子资料夹的 js 档

不是所有的东西都支援这种语法,有不少其实只支援 * 而已的,不过以 Node.js 而言,有 node-glob (gulp 底下用的),另一个系列用到,同时也是很常用的 globby ,还有 globby 底下的 fast-glob ,或是单纯检查路径是不是符合 glob 的 micromatchminimatch

gulp-babel

终於要来用第一个 plugin 了,不过其实用起来也很简单

const { src, dest } = require('gulp')
const babel = require('gulp-babel')

exports.build = async function build () {
  return src('./src/**/*.js')
    // 大部份的 plugin 都是像这样,夹在 src 跟 dest 中间,就能处理档案了
    .pipe(babel(/* 通常这边可以放选项 */))
    .pipe(dest('lib'))
}

cleanup task

这边再来用另一个 task ,这次用的是 del ,它能递回的删掉档案与资料夹,并且会回传 Promise

const del = require('del')

exports.cleanup = function cleanup () {
  // 回传 Promise 让 gulp 知道何时跑完
  return del('./lib')
}

两个加起来就有一个能清理上次的档案与用 babel 重 build 的功能了:

const { src, dest, series } = require('gulp')
const babel = require('gulp-babel')
const del = require('del')

exports.cleanup = function cleanup () {
  return del('./lib')
}

exports.build = async function build () {
  return src('./src/**/*.js')
    .pipe(babel())
    .pipe(dest('lib'))
}

exports.default = series(cleanup, build)

另外除了 series 这个照顺序执行的,还有 parallel 这个可以同步执行的,两个也可以组合:

const { series, parallel } = require('gulp')

async function a() {}
async function b() {}
async function c() {}
async function d() {}

exports.default = series(a, parallel(b, c), d)

这样就是先执行 a ,然後同步执行 b 跟 c ,最後再 d

另外 gulp 还有 watch 的功能,有兴趣可以去看看,因为现在其实也不常用了,我就不介绍太多了,下一篇来讲 gulp 内部在做什麽


<<:  语音服务-语音转换文字范例(from-file code)

>>:  谁说低代码平台上就不能写自己的CSS

Day 20 「就是真诚」TDD 的实弹演习:Magneto Effect

打球像做人 上图是笔者几年前拿网路上的图来东凑西凑,拼出来的桌面,本只是拿来练 Photo Imp...

Day21:[排序演算法]Heap Sort - 堆积排序法

heap sort的原理是采用max heap这种资料结构来做排序,max heap是一种bina...

国外便宜独服收集

此文仅收集便宜的独立服务器,排名不分先后,不定期更新,有不对的或想投稿的请在在评论区评论 Onepr...

Day13 在图表做标记仿前朝的飘逸 就当我为highlight你伏笔

Text marker and cursor marker 继昨天的cursor外,我们也常需要在...

Day 15 - Spring Boot 注册与登入

前面几篇已经完成了资料库的基本操作跟使用Thymeleaf 呈现页面,接下来才真正要踏入Spring...