認識 Gulp.js — 建立 Gulp 任務
認識 src、pipe、dest、series 和 parallel 方法
概述
本文會深入介紹 Gulp 任務中的重要方法:src
、pipe
、dest
、series
、parallel
。
這些方法可以用來建立 Gulp 基本任務,或是將基本任務串連起來組成複合任務。
Gulp 官方文件並沒有區別基本任務和複合任務。由於兩者的寫法和一些特性差異,在本文中會介紹這兩種任務:
- 基本任務:由
src
、pipe
和dest
組成的任務,對應到一則 JavaScript 函式。 - 複合任務:由
series
或parallel
組裝多個基本任務而成的複雜任務。
認識基本任務
Gulp 基本任務是由 src
、pipe
和 dest
組成的任務,對應到一則 JavaScript 函式。
了解如何使用 src
、pipe
和 dest
可以讓我們更清楚如何撰寫 Gulp 任務。
src():用 glob 格式指定檔案來源
src()
用來指定要處理的檔案或資料夾路徑。
指定路徑時,要使用 glob 格式。glob 是一種用來表示檔案路徑的格式,可以寫出明確的檔案路徑或搭配特殊符號一次指定多個檔案。
如果來源為明確的單個檔案,在 src()
裡提供檔案路徑字串;如果是好幾個明確的檔案,用陣列標明所有檔案的路徑。
// gulpfile.js
// 來源為單一檔案
function singleFileTask () {
return src('css/style.css')
.pipe(…)
.pipe(dest(…))
}
// 來源為多個檔案
function multiFilesTask () {
return src(['css/style.css', 'css/mobile.css'])
.pipe(…)
.pipe(dest(…))
}
要注意的是,要提供相對於 gulpfile.js
的檔案路徑。
glob 格式也可以搭配特殊字元使用,能夠一次指定多個符合條件的檔案。
常見的 glob 特殊字元:
*
:0 到多個任意字元**
:0 到多層的資料夾?
:1 個任意字元,不區分大小寫!
:否定字元,排除指定的檔案和資料夾
一起來了解這些特殊字元的用法:
// gulpfile.js
// 'styles' 資料夾內 (第一層) 所有 CSS 檔案
src('styles/*.css')
// 'js' 資料夾內 (不論內部有多少層資料夾) 的所有 index.js 檔案
// ('js/index.js'、'js/core/index.js'、'js/core/apis/index.js' 等等)
src('js/**/index.js')
// 'img' 資料夾內 (不論內部有多少層資料夾) 的所有 PNG 檔案
// ('img/brand.png'、'img/icon/add.png'、'img/pages/banners/sale.png' 等等)
src('img/**/*.png')
// 'img' 資料夾內 (第一層), 所有名稱為任何字元 + ig 的 PNG 檔案
// (例如:big.png, dig.png, Pig.png, Fig.png)
src('img/?ig.png')
// 'app' 資料夾裡所有的 js 檔案,但是不包含 'node_modules' 資料夾
// 換句話說,'node_modules' 內任何 js 檔案皆略過不處理
// 必須先列出要處理的項目,最後才列出不要的項目 (也就是帶有 ! 的項目)
src(['app/**/*.js', '!node_modules/**'])
要注意的是,使用 !
時,src()
中的檔案路徑要用陣列標示:先舉出要處理的檔案路徑,最後才舉出要排除的檔案路徑 (也就是加上 !
的路徑)。
除了上述介紹的特殊字元外,glob 另有其他的特殊字元可以搭配使用。然而,對於一般專案來說,只要檔案放置得宜,*
和 **
已可滿足大多數需求。
src()
找到指定檔案後,會將這些檔案轉換為 Node.js stream,再轉給接下來的 pipe()
處理。
pipe():結合外掛程式進行處理
pipe
是 Node.js stream 的內建方法。一個 pipe()
會搭配一個外掛程式,相等於一個處理步驟,也是 Gulp 任務中最重要的部份。
取得 src()
提供的 Node.js stream 後,pipe()
會依照外掛程式的功能進行處理。使用外掛程式時,可能要額外提供一些設定值 (參考外掛程式說明文件)。
pipe()
處理完後,會再回傳 stream 給下一個 pipe()
接力處理。串接多個 pipe()
即可將多個步驟串連成一組工作流程。
dest():輸出檔案到指定目的地
dest()
負責將上一個 pipe()
處理完的資料輸出到目的地。
dest()
要放在 pipe()
中使用,負責將上一個 pipe()
傳來的 stream 以檔案格式輸出到指定的位置。輸出路徑用字串標示。
試寫第一個基本任務
有了前面介紹的 src
、pipe
和 dest
概念後,就可以來模擬撰寫第一個基本任務。
任務需求:
- 壓縮所有 HTML 檔案,同時去除裡面不必要的空格。
- 壓縮後的 HTML 檔案輸出到 dist 資料夾。
能夠壓縮 HTML 檔案的外掛程式眾多,在這裡以 gulp-htmlmin 作為外掛程式。
// gulpfile.js
// 匯入 Gulp 方法和外掛程式
const { dest, src } = require("gulp");
const htmlmin = require("gulp-htmlmin");
// 建立任務函式
function minifyHTML() {
// 來源指定為專案資料夾內所有 HTML 檔案
return src("./**/*.html")
// 使用外掛程式進行壓縮,加上壓縮空白字元功能
.pipe(htmlmin({collapseWhitespace: true }))
// 輸出檔案至 'dist' 資料夾
.pipe(dest("dist"));
}
認識複合任務
了解如何建立基本任務後,一起來了解如何將多個基本任務組裝成複合任務。
Gulp 提供 series
和 parallel
方法,可將多個任務組合成複合任務,控制基本任務的執行順序:
series(task1, task2, ..., taskN)
:依序從 task1 執行到 taskN。適合用在基本任務有相依性/特定執行順序時使用,確保處理結果正確。parallel(task1, task2, ..., taskN)
:同時執行所有任務。適合當基本任務彼此獨立時使用,能夠縮短整體處理時間。
用 series
和 parallel
組合基本任務就能建立複合任務。series()
和 parallel()
也可以作為彼此的子任務:
// gulpfile.js
// 匯入 Gulp 方法和外掛程式
const { dest, src, series, parallel } = require("gulp");
const plugin1 = require("plugin1");
const plugin2 = require("plugin2");
const plugin3 = require("plugin3");
const plugin4 = require("plugin4");
// 建立基本任務
function task1 () {…}
function task2 () {…}
function task3 () {…}
function task4 () {…}
// 複合任務:依序從 task1 執行到 task4
series(task1, task2, task3, task4)
// 複合任務:同時執行四個任務
parallel(task1, task2, task3, task4)
// 複合任務:執行 task1,接著執行 parallel(task2, task3), 最後執行 task4
series(task1, parallel(task2, task3), task4)
// 複合任務:同時執行 task1、series(task2, task3) 和 task4
parallel(task1, series(task2, task3),task4)
// 其他可能任務組合…
不要重複執行任務
使用 series
和 parallel
組裝基本任務時,記得避免重複執行特定任務,以免發生錯誤或拉長處理時間。
設計不良的複合任務 (取自 Gulp 官網範例):
// gulpfile.js
const { series, parallel } = require('gulp');
const clean = function(cb) {
// body omitted
cb();
};
const css = series(clean, function(cb) {
// body omitted
cb();
});
const javascript = series(clean, function(cb) {
// body omitted
cb();
});
exports.build = parallel(css, javascript);
在上述範例中,css
和 javascript
複合任務都會執行clean
基本任務。也就是,clean
任務會執行兩次。
由於 clean
任務執行一次後就會清除目標檔案,再執行一次 clean
任務並沒有意義。另外,執行第二次 clean
任務時,指定的目標檔案已不存在,找不到要處理的檔案可能導致錯誤發生。
設計較佳的任務 (取自 Gulp 官網範例):
// gulpfile.js
const { series, parallel } = require('gulp');
function clean(cb) {
// body omitted
cb();
}
function css(cb) {
// body omitted
cb();
}
function javascript(cb) {
// body omitted
cb();
}
exports.build = series(clean, parallel(css, javascript));
比較好的作法是將重覆用到的 clean
任務抽取出來。執行一次 clean
任務後,再同時執行 css
和 javascript
任務。如此一來,執行結果能夠滿足需求,不會重覆執行導致錯誤發生。
試寫第一個複合任務
有了前面介紹的 series
和 parallel
概念後,就可以來模擬撰寫第一個複合任務。
任務需求:
- 將 Markdown 檔案轉換成 HTML 檔案。
- 壓縮 HTML 檔案,同時去除裡面不必要的空格。
- 壓縮後的 HTML 檔案輸出到 dist 資料夾。
在這裡會使用的外掛程式:
- gulp-remarkable:將 Markdown 轉換成 HTML。
- gulp-htmlmin:壓縮 HTML 內容。
// gulpfile.js
// 匯入 Gulp 方法和外掛程式
const { dest, src, series } = require("gulp");
const markdown = require("gulp-remarkable");
const htmlmin = require("gulp-htmlmin");
function convertMarkdownToHTML() {
return src('readme.md')
.pipe(markdown())
.pipe(dest('dist'))
}
function minifyHTML() {
return src("dist/readme.html")
.pipe(htmlmin({collapseWhitespace: true }))
.pipe(dest("dist"));
}
exports.build = series(convertMarkdownToHTML, minifyHTML)
從流程順序思考,必須先將 Markdown 轉換成 HTML 後才能進行壓縮,代表任務之間有相依性,因此使用 series
方法安排任務。
convertMarkdownToHTML
任務會將 Markdown 轉換成 HTML,輸出 HTML (readme.html) 至 dist 資料夾。minifyHTML
任務接著處理 readme.html,再將結果輸出至 dist 資料夾。由於輸出檔案與原有檔案同名,會自動覆蓋之前的檔案。
結論
Gulp 基本任務由 src
、pipe
和 dest
組成,對應到一則 JavaScript 函式。在 src
內要用 glob 格式指定來源檔案;在 pipe
內指定外掛程式;在 dest
內指定檔案輸出位置。
Gulp 複合任務由 series
或 parallel
組裝多個基本任務而成。series
能讓任務依序執行,parallel
能讓任務同時執行。要注意是否有任務重覆執行,以免拉長處理時間或發生錯誤。
認識 Gulp.js 系列
想要再多瀏覽 Gulp.js 4 相關教學,別錯過下列文章!