RexHung's Blog

塵世中一個前端迷途小書僮!

0%

[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 參數,詳細的介紹可參考此處

首先我們要記得第一個 APIsrc(),意思就是從哪個路徑讀取檔案,使用方式和前方一樣,先引入 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())

這邊就會學到我們接下來第二個基本的 APIdest()

如果不想理解上方這麼多細節的話,請先在專案底下創建 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 所以只好把日期往後延一天了。