Grunt 一直是前端領域構建工具(任務運行器或許更準確一些,由於前端構建只是此類工具的一部分用途)的王者,然而它也不是毫完好陷的,近期風頭正勁的 gulp.js 隱隱有取而代之的態勢。那麼,到底是什麼使得 gulp.js 備受關注呢?javascript
Grunt 之殤
gulp.js 的做者 Eric Schoffstall 在他介紹 gulp.js 的 presentation 中總結了 Grunt 的幾點不足之處:html
-
插件很難遵照單一責任原則。由於 Grunt 的 API 設計缺憾,使得許多插件不得不負責一些和其主要任務無關的事情。好比說要對處理後的文件進行改名操做,你可能使用的是
uglify
插件,也有可能使用的是concat
插件(取決於工做流的最後一個環節是誰)。前端個人見解:這或許是個問題,對不少人來講 Grunt 插件多少存在「職責不明」和「越俎代庖」的狀況。在我看來,這也是 Grunt 一個設計思想:把對文件的操做抽象爲一個獨立的組件(Files),任何插件都以相同的規則來使用它。遺憾在於,使用它的過程發生在每一個插件的獨立配置對象裏,因此總給人一種「把不應這個插件作的事情丟給它來作」的彆扭感受。html5
-
用插件作一些原本不須要插件來作的事情。由於 Grunt 提供了統一的 CLI 入口,子任務由插件定義,由 CLI 命令來調用執行,所以哪怕是很簡單的外部命令(好比說運行
karma start
)都得有一個插件來負責封裝它,而後再變成 Grunt CLI 命令的參數來運行,畫蛇添足。java個人見解:舉雙手雙腳同意!node
-
試圖用配置文件完成全部事,結果就是混亂不堪。規模較大,構建/分發/部署流程較爲複雜的項目,其
Gruntfile
有多龐雜相信有經歷的人都有所體會。而 gulp.js 奉行的是「寫程序而不是寫配置」,它走的是一種 node way。git個人見解:對於 node.js 開發者來講這是好事,符合他們的一向做風;不過對於那些純前端工程師來講(數量不小),這彷佛沒有什麼顯著的改善。何況近來 Grunt 社區涌現了很多插件來幫助開發者組織/管理/簡化臃腫的
Gruntfile
,效果都還不錯。因此關於這一點,就見仁見智吧。github -
落後的流程控制產生了讓人頭痛的臨時文件/文件夾所致使的性能滯後。這是 gulp.js 下刀子的重點,也是本標題裏「流式構建」所解決的根本問題。流式構建改變了底層的流程控制,大大提升了構建工做的效率和性能,給用戶的直觀感受就是:更快。sql
個人見解:關於流式構建,短短几句話沒法講清它的前因後果,可是在 node.js 的世界裏,
streaming
確實是相當重要的。我推薦一份閱讀材料:Stream Handbook,讀過以後相信內心就有數了。shell
做爲對比和總結,做者列出了 gulp.js 的五大特色:
- 使用 gulp.js,你的構建腳本是代碼,而不是配置文件;
- 使用標準庫(node.js standard library)來編寫腳本;
- 插件都很簡單,只負責完成一件事-基本上都是 20 行左右的函數;
- 任務都以最大的併發數來執行;
- 輸入/輸出(I/O)是基於「流式」的。
gulp.js 之道
gulp.js 的官方文檔都在 Github 上,本文是一個簡介,更具體的細節還請自行閱讀文檔。在這裏我就 gulp.js 的安裝和使用流程作一個簡述,先一塊兒來領略一下 gulp.js 的風采吧。
第一步:安裝命令行工具
$ npm install -g gulp
第二步:在你的項目下把 gulp 安裝爲開發依賴組件(假設你已經建立好了 package.json
)
$ cd <YOUR_PROJECT>
$ npm install gulp --save-dev
第三步:在項目的根路徑下建立 Gulpfile.js
,初始內容爲:
var gulp = require('gulp');
gulp.task('default', function () {
});
第四步:運行!
$ gulp
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 API -
gulp.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
也會頗有用。接着咱們須要先在項目下安裝這些插件:
$ npm install
最後咱們完成全部任務的編寫,完整的代碼以下:
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']);
能夠看出,基本上全部的任務體都是這麼個模式:
gulp.task('任務名稱', function () {
return gulp.src('文件')
.pipe(...)
.pipe(...)
// 直到任務的最後一步
.pipe(...);
});
很是容易理解!獲取要處理的文件,傳遞給下一個環節處理,而後把返回的結果繼續傳遞給下一個環節……直到全部環節完成。pipe
就是 stream
模塊裏負責傳遞流數據的方法而已,至於最開始的 return
則是把整個任務的 stream
對象返回出去,以便任務和任務能夠依次傳遞執行。
或許寫成這樣會更直觀:
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
等等 - 嘗試編寫適合本身工做流程和習慣的任務,若是它工做良好,把它作成插件發佈給你們吧!