本文主要從兩個方面介紹Gulp:一,Gulp相對於Grunt的優勢; 二,Gulp的安裝和使用流程
Gulp相對於Grunt的優勢
gulp.js 的作者 Eric Schoffstall 在他介紹 gulp.js 的 presentation 中總結了 Grunt 的幾點不足之處:
- 外掛很難遵守單一責任原則。因為 Grunt 的 API 設計缺憾,使得許多外掛不得不負責一些和其主要任務無關的事情。比如說要對處理後的檔案進行更名操作,你可能使用的是
uglify
外掛,也有可能使用的是concat
外掛(取決於工作流的最後一個環節是誰)。我的看法:這或許是個問題,對很多人來說 Grunt 外掛多少存在“職責不明”和“越俎代庖”的情況。在我看來,這也是 Grunt 一個設計思想:把對檔案的操作抽象為一個獨立的元件(Files),任何外掛都以相同的規則來使用它。遺憾在於,使用它的過程發生在每個外掛的獨立配置物件裡,所以總給人一種“把不該這個外掛做的事情丟給它來做”的彆扭感覺。
- 用外掛做一些本來不需要外掛來做的事情。因為 Grunt 提供了統一的 CLI 入口,子任務由外掛定義,由 CLI 命令來呼叫執行,因此哪怕是很簡單的外部命令(比如說執行
karma start
)都得有一個外掛來負責封裝它,然後再變成 Grunt CLI 命令的引數來執行,多此一舉。
我的看法:舉雙手雙腳贊成!
- 試圖用配置檔案完成所有事,結果就是混亂不堪。規模較大,構建/分發/部署流程較為複雜的專案,其
Gruntfile
有多龐雜相信有經歷的人都有所體會。而 gulp.js 奉行的是“寫程式而不是寫配置”,它走的是一種 node way。我的看法:對於 node.js 開發者來說這是好事,符合他們的一貫作風;不過對於那些純前端工程師來說(數量不小),這似乎沒有什麼顯著的改善。況且近來 Grunt 社群湧現了不少外掛來幫助開發者組織/管理/簡化臃腫的
Gruntfile
,效果都還不錯。所以關於這一點,就見仁見智吧。 - 落後的流程控制產生了讓人頭痛的臨時檔案/資料夾所導致的效能滯後。這是 gulp.js 下刀子的重點,也是本標題裡“流式構建”所解決的根本問題。流式構建改變了底層的流程控制,大大提高了構建工作的效率和效能,給使用者的直觀感覺就是:更快。
我的看法:關於流式構建,短短几句話無法講清它的來龍去脈,但是在 node.js 的世界裡,streaming
確實是至關重要的。我推薦一份閱讀材料:Stream Handbook,讀過之後相信心裡就有數了。
作為對比和總結,作者列出了 gulp.js 的五大特點:
- 使用 gulp.js,你的構建指令碼是程式碼,而不是配置檔案;
- 使用標準庫(node.js standard library)來編寫指令碼;
- 外掛都很簡單,只負責完成一件事-基本上都是 20 行左右的函式;
- 任務都以最大的併發數來執行;
- 輸入/輸出(I/O)是基於“流式”的。
Gulp的安裝和使用流程
第一步:安裝命令列工具
1 |
$ npm install -g gulp |
第二步:在你的專案下把 gulp 安裝為開發依賴元件(假設你已經建立好了 package.json
)
1 2 |
$ cd <YOUR_PROJECT> $ npm install gulp --save-dev |
第三步:在專案的根路徑下建立 Gulpfile.js
,初始內容為:
1 2 3 4 |
var gulp = require('gulp'); gulp.task('default', function () { }); |
第四步:執行!
1 2 3 4 |
var gulp = require('gulp'); gulp.task('default', function () { }); |
So far so good! 看起來和 Grunt 沒差太遠吧?的確如此,gulp.js 的學習曲線還是相當平緩的。接下來,為了能夠順利的編寫構建指令碼,我們來學習幾個核心的 API 函式——別擔心,gulp.js 的 API 非常簡單,我們只需要瞭解四個就足以應對絕大多數的指令碼編寫了(而且用過 Grunt 的話,這四個都不是什麼新鮮貨)。
gulp.task(name[, deps], fn)
:註冊任務
name
是任務名稱;deps
是可選的陣列,其中列出需要在本任務執行要執行的任務;fn
是任務體,這是 gulp.js 的核心了,需要花時間吃透它,詳情見此。gulp.src(globs[, options])
:指明原始檔路徑
用過 Grunt 的話,globs
一定不會陌生,這裡沒什麼變化;options
是可選的,具體請檢視 gulp.js APIgulp.dest(path)
:指明任務處理後的目標輸出路徑gulp.watch(glob[, options], tasks)/gulp.watch(glob[, options, cb])
:監視檔案的變化並執行相應的任務。你沒看錯,watch
作為核心 API 出現在 gulp.js 裡了,具體用法還是要多看文件,不過接下來我們會演示簡單的例子。
範例
我們練習一個最常見的範例,寫一個 node.js 程式時所需要的構建指令碼。為此我們要做三件事情(括號內列出對應外掛的名字,更多外掛請到此處尋找):
- 語法檢查 (
gulp-jshint
) - 合併檔案 (
gulp-concat
) - 壓縮程式碼 (
gulp-uglify
) - 檔案重新命名(
gulp-rename
)
另外,我們可能還需要檔案更名操作,所以 gulp-rename
也會很有用。接著我們需要先在專案下安裝這些外掛:
1 |
$ npm install <PLUGIN_NAME> --save-dev |
最後我們完成所有任務的編寫,完整的程式碼如下:
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 29 30 |
var gulp = require('gulp'); var jshint = require('gulp-jshint'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); var rename = require('gulp-rename'); // 語法檢查 gulp.task('jshint', function () { return gulp.src('src/*.js') .pipe(jshint()) .pipe(jshint.reporter('default')); }); // 合併檔案之後壓縮程式碼 gulp.task('minify', function (){ return gulp.src('src/*.js') .pipe(concat('all.js')) .pipe(gulp.dest('dist')) .pipe(uglify()) .pipe(rename('all.min.js')) .pipe(gulp.dest('dist')); }); // 監視檔案的變化 gulp.task('watch', function () { gulp.watch('src/*.js', ['jshint', 'minify']); }); // 註冊預設任務 gulp.task('default', ['jshint', 'minify', 'watch']); |
可以看出,基本上所有的任務體都是這麼個模式:
1 2 3 4 5 6 7 |
gulp.task('任務名稱', function () { return gulp.src('檔案') .pipe(...) .pipe(...) // 直到任務的最後一步 .pipe(...); }); |
非常容易理解!獲取要處理的檔案,傳遞給下一個環節處理,然後把返回的結果繼續傳遞給下一個環節……直到所有環節完成。pipe
就是 stream
模組裡負責傳遞流資料的方法而已,至於最開始的 return
則是把整個任務的 stream
物件返回出去,以便任務和任務可以依次傳遞執行。
或許寫成這樣會更直觀:
1 2 3 4 5 6 7 8 |
gulp.task('task_name', function () { var stream = gulp.src('...') .pipe(...) .pipe(...) // 直到任務的最後一步 .pipe(...); return stream; }); |
至此,你已經可以使用 gulp.js 完成絕大多數的構建工作了。下一步,我也為你準備了幾條建議:
- 花點時間瀏覽一下 gulp.js 外掛庫,大致瞭解下利用已有的外掛你都可以做哪些事情
- 對於常用的外掛,仔細閱讀它們自己的文件,以便發揮出它們最大的功效
- 抽時間學習 gulp.js API,特別是
gulp.task()
裡關於任務體的詳細描述,學會如何執行回撥函式(callback),如何返回promise
等等 - 嘗試編寫適合自己工作流程和習慣的任務,如果它工作良好,把它做成外掛釋出給大家吧!