folder_open

30 天打造 MERN Stack Boilerplate

arrow_right
article

Day 04 - Automation

Day 04 - Automation

所有良好的專案都需要健全的自動化系統在背後支撐,雖然實作自動化需要成本,但之後帶來的效益絕對會讓你一邊寫 Code 一邊笑到合不攏嘴。

何謂自動化?

#

我們提過專案的開發是有分 Development、Test 還有 Production 週期的,不同階段需要建置(Build)不同的檔案配置或環境,這些瑣碎的雜事傳統上是人工在 Terminal 上打出一系列的指令,包括編譯、轉譯、複製檔案、刪除中繼暫存檔、...等等,熟悉 Linux 的人可能會自己寫個 Shell Script 來執行這些例行性的流程,這就是自動化的精神。

再舉個例子,寫過 C 的人應該有 87% 知道 Makefile 這東西,只要把建置流程寫在 Makefile 內,再到 Terminal 輕鬆打下 四個字母,撲通一聲就產生可執行檔了。在這個例子中,make 的過程就是一種自動化。

為何專案需要自動化?

#

所以究竟為什麼要在專案中落實自動化呢?因為人為操作的過程有太多不謹慎,可能打錯指令,可能執行的流程顛倒了,還要浪費腦容量來背指令,身為一個工程師,怎麼可以容許自己做這麼蠢的事呢?另外,團隊的開發也是需要有一套共同的操作流程,不可能每一位團隊裡的成員都熟悉產品的建置流程,所以最好訂定一套一致的、語意明確的、簡易的自動化指令,例如要跑測試就統一執行 ,要部署程式就統一打

最後幫各位歸納一下自動化帶來的優點:

  • 減少人為失誤
  • 省時、省力、省腦容量
  • 標準化團隊協作的流程

Npm

#

前面所述是給各位一個大方向,現在我們回到 Node 的世界。大家所熟悉的 Npm 除了可以安裝套件之外,還有另一個重要功能,就是執行腳本。

馬上給各位一個實用的例子,請在你的任意 補上以下 的部分:

{
...
"scripts": {
"clean": "rm -rf ./build"
},
"devDependencies": {
...
},
"dependencies": {
...
}
}

接著執行 就會自動執行對應的 指令了。

而 npm 其實也內建了幾個快速指令對應到 package.json 中的 scripts,例如: 對應到 scripts 中的 對應到 scripts 中的 ,其餘依此類推,完整對應的指令可以參考 Npm 官方文件 npm-scriptsopen_in_new

實務上我們會把 scripts 拆的越細越好,一個 script 只專注完成一件工作,如果需要自動化執行一系列的大工作,就用 將這些小工作串聯起來執行即可。一樣給各位一個實際用在 Boilerplate 中的例子:

{
"scripts": {
"test": "npm run lint && npm run mocha",
"lint": "./node_modules/.bin/eslint --config=./.eslintrc.json ./src",
"mocha": "mocha specs/index.js --timeout 10000",
},
}

如此一來我只要執行 就可以自動檢查 Coding Style,接著執行 Mocha 的測試腳本了。

善用 Gulp,勿用 Grunt

#

利用 Npm 來實現自動化對於小專案是很足夠的,但對於 Boilerplate 則需要更進階的工具了,目前有兩套廣受好評的自動化工具:Gulp 與 Grunt。

先強調 Gulp 絕對遠勝於 Grunt。

Grunt 是 Configuration-Based,所以使用之前必須熟悉 Grunt 的設定檔結構,說難聽點就是我他媽要浪費腦容量記這雞八結構,而且可讀性之差讓我實在懶得吐槽,其次,Grunt 在處理某些複雜的操作時,必須要先建置出中繼檔案,說白話點就是它會製造垃圾,最後我最不能忍受的是,有少量需要高度客製化的流程過於複雜,已經超出 Configuration-Based 機制的極限了,所以根本就做不出來!做不出來!做不出來啊!

罵完了 Grunt 回來看看我們可愛又親切的 Gulp:

  • Program-Based
  • 可讀性高
  • 容易擴充
  • 使用 Streaming 搭配 pipe 語法,所以不會產生中繼檔案
  • 無論多複雜的流程都能以優雅的方式寫成腳本

使用 Gulp

#

接著我們就直接看看 Gulp 的使用方式吧:

  1. 在專案內建立一份
  2. 以 JS 撰寫各種 Gulp Tasks
  3. 執行

Gulp Task

#

Gulp 中最重要的元素就是 Gulp Task,它是一個個相依或獨立的工作流程,每一個 Gulp Task 寫法都是單純且一致的。

gulp.task([dependencyTask1, dependencyTask2, ...], taskName, function() {
// what this task is going to do
});

範例

#

讓我們來看個簡單的 gulpfile.js 範例(擷取自 Boilerplateopen_in_new):

var path = require('path');
var gulp = require('gulp');
var del = require('del');
var mergeStream = require('merge-stream');
var paths = {
componentStyles: [
'./src/common/components/**/*.scss',
'./src/common/components/**/*.less',
'./src/common/components/**/*.styl',
'./src/common/components/**/*.css',
],
statics: './src/public/**/*',
targetDir: 'build',
};
// clean build files
gulp.task('clean', function() {
return del.sync([
paths.targetDir,
]);
});
// copy static files
gulp.task(['clean'], 'copy', function() {
var staticTask = gulp
.src(paths.statics)
.pipe(gulp.dest(path.join(paths.targetDir, 'public')));
var componentStyleTask = gulp
.src(paths.componentStyles)
.pipe(gulp.dest(path.join(paths.targetDir, 'common/components')));
return mergeStream(staticTask, componentStyleTask);
});
gulp.task('default', function() {
gulp.start('clean', 'copy');
});

當我執行 時,就會清除 build 資料夾;當我執行 時,會先確保 gulp clean 已經完成,接著才複製靜態檔案和各個 styles 檔案;當我執行 時,會同時執行 gulp clean 及 gulp copy。另外,如果直接執行 而不指定 task name,預設就是執行 gulp default。

在本篇教學文中提到 Gulp 的目的不是要讓大家在本文中學會使用 Gulp,而是想讓讀者們知道有這樣的好工具,真正要學會它的話還是請上官網或者 Google 學習資源會比較有效率!

Boilerplate 的自動化

#

在我們的 Boilerplate 中大量地使用了自動化,包括 Npm 及 Gulp,來完成以下的工作:

  • 自動化 Linting
  • 自動化 Build
  • 自動化 Testing
  • 自動化 Deploy

我傾向使用 Npm 實現 CLI 指令的自動化(例如 Linting),如有牽扯到複雜的流程或邏輯檢查時,再轉用 Gulp(例如 Build、Deploy)。

自動化的過程是痛苦的

#

乍看之下自動化百利無一害,但其實要在專案裡面套用自動化是需要經歷陣痛期的,你需要仔細看過 CLI Tool 的說明,或是 Gulp Plugin 要如何使用、如何設定、參數怎麼下,經歷這些過程時肯定會讓你感到挫折、感到煩燥。實做專案的自動化可能是個痛點,但我還是強烈建議各位讀者要扎扎實實地面對它,因為手動的成本真的太高昂了,長痛不如短痛!

筆者心情小補帖

#

還記得我剛接觸自動化時踩了一個大雷...

那時觀察 Open Source 界的風向發現大家都在用 Grunt,所以我也跟風先學 Grunt,不是我在亂蓋,但我真的是花了整整一個星期努力研究這糞物,瘋狂 Google 各種教學文卻一無所成,完全寫不出我想要的自動化流程。最後放棄 Grunt,隨手花了 30 分鐘看個 Gulp 官方介紹,X你X的我就這樣學會 Gulp 了,隨後花了 2 ~ 3 個小時就建出理想的流程...。至今每當我想起 Grunt 都還是覺得噩夢一場。