[Tool Note] — 關於Gulp

終於講到飲料杯了

Introduction & 前言

如果你為了前端切版每次都要從 HTML 開始建立檔案,然後 CSS 再來 JavaScript…等等,而且碰到多頁面的版面手足無措,那你就可以考慮看看這篇文章了。

開始前先提醒一下文章較長,您可以重頭開始看怎麼建立 Gulp 抑或是滑到下方選擇觀看 安裝後的Gulp筆記 或 **關鍵懶人包目錄**!!

之前玩過 Webpack 時就一直聽過 Gulp,但是一直沒有去研究過,一直以為兩個是差不多性質的東西,應該一個就夠用了,但是藉由這次的六角切版直播班真的接觸到了 Gulp,我想是時候好好研究研究一番了。

溫馨提醒:如果還沒看過 Webpack 或是您已經是 Webpack 高手,建議可以先參考看看我的前兩篇文章,如果您只是想研究 Gulp 其實只看本篇文章也沒關係,[Tool Notes] — 關於Webpack #1 - 第一次就上手[Tool Notes] — 關於Webpack #2 - Babel? 如果有歡念錯誤歡迎各路高手不吝社指教。

地雷警告:由於 Gulp 自己也還不是很熟,本篇文章就是用來筆記加練習的,如果有觀念錯誤也歡迎各路高手不吝社指教。


關鍵懶人包目錄

這邊提供給已經安裝過並且懂的大概的 npm 操作流程,只想看 Gulp 怎麼操作的人:

  1. 任務執行基礎的兩個API - 同步任務 及 非同步任務

  2. 檔案操作基礎的兩個API - 取得 及 輸出

  3. 最後關鍵比一比懶人包

  4. 範例檔下載


Gulp 是什麼?

Webpack 一樣,剛學前端在切版時我們很長就是創建一個新的 HTML CSS JavaScript…等等,碰到多頁面的版面我們就是 index_1.html index_2.html index_3.html,久了就會覺得麻煩,身為工程師,不就是要把麻煩的事情簡單化嗎?!

所以在後來就出現了基於 Node.js(剛接觸前端不久後可能就會常常碰到)、NPM 的**自動化建構工具(註1)**,幫你省去很多重複且枯燥的步驟。

註1:就字面上的意思大概已經解釋了 87% 簡單說就是下個指令之後幫你執行一些不必重複做的工作,比方說 編譯、壓縮、命名或是開發時用一種寫法,上正式環境換一種寫法。

使用說明?

自動化建構工具

其實仔細看 GulpWebpack 的官網介紹,會發現兩個非常像,但你實際使用,會發現 Gulp 更注重於 任務 的概念,而 Webpack 注重於 打包效能 方面。

但其實兩種工具官方都有很詳盡的介紹,只要跟著走,基本都沒什麼問題!甚至最後你還能將兩者合併使用呢!

但筆記不是寫心情日記,今天還是要認真的跑過一次流程,下面開始就會介紹怎麼把 Gulp 跑過一遍。


釣竿來

這邊建議還是要有一點 JavaScript 的基礎,如果沒有沒關係,如果有會比較輕鬆。

首先您必須要先安裝 Node.js 之後才能使用 npm,這邊先不用搞懂他們在幹嘛,只要記得之後常常會碰到 node_module 而這裡面放滿你會用到的前端套件

NodeJS

安裝方法很簡單,可以到 Node.js 的官方網站下載安裝檔安裝,或是參考我的這篇文章安裝 [Tool Notes] — 關於Webpack #1 - 第一次就上手 裡面有提到 Node.js 怎麼安裝。

安裝完成記得打開終端機輸入 node --version 如果安裝成功你會看到 v版本號,就是安裝成功了,記得也要輸入 npm --version 就能看到有沒有把 npm 安裝成功!

成功安裝!

趕快發車

前置作業了之後我們要安裝 全域的Gulp-cli專案裡的Gulp,前者的安裝讓使用者可以在任何地方透過 cli 指令(比如 gulp) 來下指令,後者讓使用者可以使用 gulp API 來自定義建構的任務。

首先安裝全域的 gulp-cli

1
2
3
$ npm install gulp-cli -g // 安裝全域的 gulp-cli


接著進到你要實作的專案底下輸入以下指令(安裝路徑記得要對喔!!),縮寫的 iinstall 縮寫 -D–save-dev 的縮寫,意指只安裝到 package.jsondevDependencies,不理解的話先略過沒關係

1
$ npm i -D gulp // 安裝 gulp 到專案

安裝完成輸入 gulp --version ,如果看見下圖 Local version: Unknown 就代表你目前在錯誤的目錄,一定要進到你要實作的專案底下安裝才會有版本號出現!

安裝完成


初始化專案

以往我們都是從 index.html 開始建立,這邊我們換個做法,到你要實作的專案資料夾下,先在終端機輸入 npm initnpm init -y,第一次建立建議都輸入前者,因為接下來會跳出一連串的問題給你作答,大致就是問你 專案名稱 專案介紹 作者 或是 是否要連上Git…等等,建議跑過一次流程之後再試試後面的輸入方法,後者可以省略那堆問題!

一連串的問題

因為我們是練習,所以就是一路 Enter 下來,最後會在你的資料夾內看到 package.json 檔案,這邊我們先來介紹一下這個檔案是幹嘛的。

以往我們會把需要用的套件直接用 CDN 方式插入在 index.html 裡面,就像下圖所示。

傳統套件引入作法

但是隨著引用的套件越來越多,會越來越雜亂,而且也不好管理版本,所以後來的 package.json 就是在做這件事情,裡面詳細記載你用了什麼套件,還有這個專案的詳細介紹(沒錯 就是你前面略過的那一大串),至於版本號等等還會有個檔案叫 package-lock.json 記載。

前置步驟的最後在輸入下列語法,就會安裝記載在 package.json 裡的所有套件,這也是為什麼我們最後只需要上傳 原始檔package.json 給下一個人,他就會知道你用了什麼套件並且怎麼修改,而我們也不需要把一大包套件上傳到空間上去。

1
$ npm install

除此之外使用 package.json 管理套件還有個好處,通常使用 cdn 方式引入套件,如果對方存放套件的 Server 掛掉了,或是連結甚至網路掛掉了,基本上就無法使用這個套件了,但安裝在 node_module 等於是安裝在本地,即使網路斷了,或是來源遺失了,至少你還保留一份套件檔案在這邊,前提是你必須在掛掉之前安裝Q。

安裝完成後就會出現 package-lock.json,這就會記載你安裝套件的版本號啦!


預告太長了

先別急,這邊正式要開始了。這邊開始 Gulp 做法跟 Webpack 有點像,後者會在資料夾建立 webpack.config.js,前者會在資料夾建立 gulpfile.js,這就是我們的 核心設定 檔案了。

按照官網的指示,我們先在專案資料夾創建 gulpfile.js 然後在裡面貼上下列程式碼:

1
2
3
4
5
6
7
function defaultTask(cb) {
// place code for your default task here
console.log(123);
cb();
}

exports.default = defaultTask

之後在終端機上輸入 gulp(記得要到專案目錄底下喔!),會出現下面的結果,先講解一下,終端機輸入 gulp 後面如果不帶任何字串,預設就是 gulp default,而我們要有個概念 gulp 都是以任務為出發點再跑的,意思就是跑一個 default 的任務,而前面我們已經寫好了,預設的 default 任務就是 function defaultTask(),內容就是 console.log(123);

預設任務

沒錯!到這邊你就完成了,就是這麼簡單。

就這麼簡單


不是幼幼班

當然事情沒有這麼容易,我們想做到的事情和現在還差十萬八千里遠,但放心,如果是 Webpack 的話目前應該還有二十萬八千里遠(以一個菜鳥過來人的上手經驗來說,有誤別砲我)。

在我們有了 任務 的概念之後,我們還要先有一個基礎知識,前面提到我們有說我們會安裝很多套件在 node_module 立面並且記載在 package.json,那是不是 npm install 之後出現 package-lock.json 就可以使用了呢?

這邊就要講解一下了,通常透過 Node.js 安裝的套件,在那個專案都會有一個 **進入點(在前面 npm init 的時候會問你要不要指定檔案 不要就是預設 index.js)**,而我們想使用套件就必須在 進入點 把套件 import 或是 required 近來,意思其實跟 cdnindex.html 插入一行 <link rel="stylesheet" href="./plugins/bootstrap-v4.3.1/css/bootstrap.min.css" /> 是差不多的,接著你就能在 進入點 的那隻檔案使用那個套件,最後只要你的檔案可以吃得到 進入點 就能使用套件。

Gulp 4.0 和以前的寫法可能有些許差異,因為菜雞我沒用過以前版本,但 4.0 的寫法在 4.0 以前會無法使用,例如陣列寫法改為 series()parallel()

怎麼引入套件

這邊我們會引領您記得幾個 Gulp 要記得的 API,以後您會常常使用到它!

首先這邊就以 Gulp 為例子,當我們有很多任務要執行的時候,我們該怎麼做呢?

在檔案內改為以下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 引入套件 及 套件API
const { series, parallel } = require('gulp');

// 任務一
function taskOne(cb) {
setTimeout(function(){
console.log(1);
cb();
},2000)
}

// 任務二
function taskTwo(cb) {
setTimeout(function(){
console.log(2);
cb();
},2000)
}

// 任務三
function taskThree(cb) {
console.log(3);
cb();
}

// 輸出整個任務
exports.default = series(taskOne, parallel(taskTwo, taskThree))

第一句就是我們引進套件及套件的API(方法),而 4.0 跟以往最大的差異就在多了 seriesparallel,前者為任務一個結束才會換下一個,後者為同步進行,我不管前面完成沒有,反正包在 parallel 裡的我全部都一次進行。

而範例的進行結果就是後面會等 taskOne 運行結束(記得如果沒有 Return 在 Function 立面都要設定一個 end 宣吿,例如 cb()),結束後包在 parallel 裡面的不管順序都會一起進行。

仔細看看秒數

順帶一提,最前方的 const { series, parallel } = require('gulp'); 亦可寫為 const gulp = require('gulp'); 然後在需要使用的地方加上 gulp.seriesgulp.parallel,例如最後一句可改為 exports.default = gulp.series(taskOne, gulp.parallel(taskTwo, taskThree)),意思為使用 gulp 的 API seriesparallel

除了前面寫的 cb() 當作結束任務外,任務還可以使用諸如 promiseevent emitterchild processobservable…等等,詳細可參考官方指南,如果都不用就是必須向前面寫的,要有一個 callback(例:cb())。

關鍵懶人包一

上述例子做完你已經學會了基本的兩個API,seriesparallel,只要記得要呼叫任務就是使用這兩個,前者為不同步呼叫,後者為同步呼叫!

還有什麼全都拿出來!

再學會前面兩個基本的 API 後,這邊開始我們要學另外兩個基本的 API;想要 自動化建構 我們最基本需要的事情就是 操作檔案

為什麼 GulpAPI 可以操作檔案呢?因為他接受 glob 參數,詳細的介紹可參考此處

首先我們要記得第一個 API 為 **src()**,意思就是從哪個路徑讀取檔案,使用方式和前方一樣,先引入 const gulp = require('gulp');,然後呼叫 gulp.src('./檔案路徑'),或是在最前方改為 const { src } = require('gulp');,然後使用 src('./檔案路徑')

按照官網的說法,取得檔案後會生成一個 **Node流(stream)**,意思是蝦咪呢?Steam 我還理解(誤)

翻了網上的解釋,它是一種數據處理的方法,從輸入到輸出,可以稱為一個流,他並不是像傳統的讀寫文件,一次一個,而是隨讀取的數據多寡,處理其內容,而在大數據之中就非常強大,例如檔案可能超過你的空間容量,不可能將整個文件塞進你的空間在讀取,而這時後 Node流(stream) 就發揮作用了。

流的抽象概念

抱歉我實在是找不到圖片,如果比喻有誤還請手下留情。

Node流(stream) 分為四種流:

清流?流派?

流大致可分為四種類型:

  1. Writable - 可寫入的流

  2. Readable - 可讀取數據的流

  3. Duplex - 可讀又可寫的流

  4. Transform - 在讀取過程中可以修改或轉換數據的 Duplex 流

上面貼這麼多流得介紹網站如果你英文不好可以參考這邊 中文流

繼續開車

接著繼續學習我們的 Gulp API,簡單理解下,從我們發起讀取檔案 gulp.src('./檔案路徑') 後,這時候就會產生 Node流(stream) 而這時候我們就可以在這之間去對 Node流(stream) 做動作。

然而怎麼做動作可以依靠 Node流(stream) 提供的方法 .pipe(),適用於 轉換流(Transform streams) 或是 **寫流(Writable streams)**,透過 .pipe() 我們可以把想做的事情透過方法寫在 .pipe() 內,比如 .pipe(babel())

這邊就會學到我們接下來第二個基本的 API,**dest()**。

如果不想理解上方這麼多細節的話,請先在專案底下創建 app 資料夾,裡面在創建一個 assets 資料夾,最後再放入 js 資料夾,然後創建兩個 .js 在裡面,這邊範例一個名稱為 main.js 另一個為 second.js,接著在 核心檔案 gulpfile.js 內輸入以下程式碼:

1
2
3
4
5
6
7
8
9
// 來源, 目的地
const { src, dest } = require('gulp');

function js() {
return src('./app/assets/js/*.js') // 讀取資料夾 ./app/assets/js 底下的所有 .js 檔案
.pipe(dest('./dist')) // 輸出所有被讀取的 .js 檔案
}

exports.default = js

接著在終端機輸入 **gulp(或 gulp default)**,就會開始編譯,這時候你會發現多出一個 dist 資料夾,仔細瞧,你的自動化已經踏出一大步了!

關鍵懶人包二

上述例子做完你又學會了基本的兩個API,src()dest(),只要記得想要操作檔案,就要取檔案及輸出檔案,前者為取得檔案,後者為輸出檔案,而這中間的過程可以對檔案做一些壞壞的事情想做的事情!


補充說明

上面提到的 .pipe(dest())gulp 提供的一個 API,而我們有一些多的套件都可以加入到這裡面,比方說 ES6 編譯,接著來學習怎麼使用套件吧!

先輸入 npm i -D gulp-babel @babel/core @babel/preset-env 然後在 gulpfile.js 上方引入 const babel = require('gulp-babel'); 編譯套件,接著在把前面提到的範例程式碼做一下改編:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 來源, 目的地
const { src, dest } = require('gulp');

function js() {
return src('./app/assets/js/*.js') // 讀取資料夾 ./app/assets/js 底下的所有 .js 檔案
.pipe(
babel({
presets: ['@babel/env'], // 使用預設環境編譯
})
)
.pipe(dest('./dist')) // 輸出所有被讀取的 .js 檔案
}

exports.default = js

然後到我們的 ./app/assets/js/main.js 把裡面的程式碼改為下列範例,範例為 ES6 的箭頭函示:

1
2
3
const exampleWord = () => "Hello Gulp!"

console.log(exampleWord());

之後一樣在專案底下開啟終端機輸入 gulp(或 gulp default),結果再到 ./dist 資料夾查看編譯出來的 .js 檔案,你會發現語法已經被編譯成普通的 JavaScript 了!

像是 babel() 這個 API 就是套件提供給我們的,我們只需要塞入 .pipe() 然後在看套件有什麼需要設定的;而像 模板、SCSS、複製檔案…等等,差不多都是這樣的做法,突然覺得還挺簡單得吧(當我沒說哈哈?!


你畢業了

學會四個基本的 API,其實你已經學得差不多了,接著就是剩下的套件學習,是不是覺得跟 Webpack 基礎理解差不多,只要抓到基礎得點,剩下再延伸進入研究就差不多了,雖然我還是覺得 Gulp 比較好上手。

接下來這邊會提供一份範例,有興趣的同學可以抓下來研究,也可以自己重無到有去創建,記住只要抓住那四個基礎,如何開始任務,如何解決同步非同步任務,如何取得輸出檔案,你就能慢慢上手。

恭喜幼幼班畢業


超級比一比

雖然這樣比好像不是正確的,但目前自己練習完 WebpackGulp 之後的整理心得如下圖,如果想入門一探這兩種自動建構工具的學習基礎:

自動化建構工具比一比

搞懂這幾個基礎點之後剩下的就是一些設定及套件研究了!


你要的魚在這

這邊提供一個範例,包含 JS 編譯、Sass/Scss 編譯、檔案複製、檔案監聽、模擬瀏覽器…等等,之後會再補上 HTML 模板部分,可至 GitHtbREADME.md 查看安裝的過程。

Gulp 範例傳送門 => 點我


Conclusion & 結論

其實以前已經想研究 Gulp 很久了,這次會接觸也是因為六角切版直播班,但因為是切版為主,課程沒有太在 Gulp 著墨,但我想工程師的本質都是想追根究底的,碰到東西還是會想研究學習一番,畢竟之後開始處理問題總不能一直拿老師的範例吧。

這次為 Gulp 初體驗,我想還有許多改進空間,像是之後還會再考慮如何把 Webpack 融入進 Gulp,而我想後者的學習門檻似乎比前者還要低一點,恰巧最近碰到需要把前端規劃出一個有效且快速的部署環境,我想 Gulp 應該會很適合我。

這邊有機會在寫下一篇 Gulp 了,感謝各路大神的觀看,如果有錯還請各路大神不吝社指教,在此小弟先謝過了!

我們都是在錯誤和學習中成長,但憑著興趣或快樂學習的速度將會是倍數成長!


參考網站


後記

這篇文其實實在 2020/05/05 晚上寫的,但 hexo 似乎無法用兩個一樣名字的 .md 所以只好把日期往後延一天了。