[按:網上介紹Gulp和Grunt安裝使用的文章很多,甚少比較二者的思路,連官方文檔都語焉不詳。我在此做一個粗陋的對比,希望能提綱挈領,加深讀者對這兩個工具的理解。]
做過點兒正經開發的同學都知道,構建工具必不可少。C時代的Make、Java的Ant、Ruby的Rake……沒有這些工具,一遍遍地點選輸入,準煩死你。
在前端和Node JS的開發中,最普及的構建工具就是Grunt。它的功能說來簡單,就是管理一系列的Task。大部分的Task都是第三方的插件,安裝好對應的NPM包,再loadNpmTasks
就可以用了。
Grunt的配置文件Gruntfile
,主要包含兩部分:
配置每個Task,包括文件從哪里,到哪里去,還有一些處理的選項
自己寫一些簡單的Task,把第三方插件提供的Task組合起來
別看這兩個事兒,輕輕松松幾百行出來了。每個Task的配置,各有各的規矩,還牽扯到插件間的配合。反正我從seed庫開始做新項目的時候,基本不敢改原來的Gruntfile,很多用不上的功能也擱那兒。留意了一下很多開源項目的Gruntfile,也都臃腫雜亂,好不到哪兒去。
Gruntfile維護起來那么困難,有幾個原因:
配置和運行分離
程序員都知道,變量的聲明和使用挨在一起,最方便理解和修改。但Gruntfile里,配置Task和調用它們的地方離得很遠,極大地增加了心智負擔。每個插件做的事太多
每個Task的結果必須寫到磁盤文件,另一個Task再讀,損害性能倒是小事,更麻煩的是讓整個過程變復雜了。
就像一個個小作坊,來料加工又返回給客戶,這中間的溝通成本、出錯機會都大大增加。配置項過多
做事多了,配置項自然也多。至少輸入和輸出的位置得配吧。每個插件的配置規則還不盡相同。用每個插件,都得去學習一番。
Gulp應運而生。
恐怕沒幾個IT人不知道Unix管道的概念。前一級的輸出,直接變成后一級的輸入。把簡單的工具組合起來,優雅地解決復雜的問題。聽起來那么熟悉呢?是的,Gulp就把這種思維用在構建過程中。
Gulp基于Node JS的一個機制,叫做stream,有點類似C++中的stream。在Node中,文件訪問、輸入輸出、HTTP連接,都是stream。Gulp的每個插件從stream中讀取輸入,做一些處理,再輸出到stream中。
每個插件不是拿來獨立使用的。相反,它專注于完成單一職責。只有把合適的插件組合起來,才能完成具體的Task。引用官方的例子,看看一個典型的Task長什么樣(略有刪減):
var paths = {
scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee']
};
gulp.task('scripts', ['clean'], function() { // 可以依賴于其它task
return gulp.src(paths.scripts) // 指定輸入
.pipe(coffee()) // 環節一
.pipe(uglify()) // 環節二
.pipe(concat('all.min.js')) // 環節三
.pipe(gulp.dest('build/js')); // 指定輸出
});
配置呢?不需要了。是不是行云流水,一氣呵成?
那我們再回頭來看看前面Grunt的幾個問題,Gulp是怎么解決的:
配置和運行分離
code over configuration,直接就在調用的地方配置。每個插件做的事太多
單一職責,依靠組合來發揮作用。就像一條自動化生產線,上一道工序的產出直接交給下一步,效率不要太高。配置項過多
既然大家都遵循同一個協議,很多配置就不需要了。
放大了看,Gulp像是一個非常貼近領域模型的DSL,而Grunt更像萬能的XML。哪個好用,無需多說。在我們制作DSL時,也有參考意義。
最后,舉一個Grunt很別扭,Gulp卻能優雅解決的例子。
做前端開發會用到一個功能叫usemin
。我們HTML中會引用到很多css和js文件。發布時,這些文件要合并、壓縮、混淆,最后生成一兩個文件。為了讓修改過的代碼繞過瀏覽器的緩存機制,要根據文件內容hash出文件名。html文件里就要引用這些新的文件名。
比較一下grunt-usemin和gulp-usemin各自README的長度,就能看出區別。
grunt.registerTask('build', [
'useminPrepare', // 準備
'concat',
'cssmin',
'uglify',
'filerev',
'usemin' // 執行
]);
grunt-usemin
分成兩步:
先從html文件中收集需要處理的js和css,傳給后續的一堆任務
它本身并不知道在實際中會調用哪些其它Task,只能用一些hack,支持固定的幾個Task。而上面的每個Task,都有自己的配置項。要把這些配置項都列出來,實在太長了。真正執行,更新html文件里的js和css引用。
而gulp-usemin
就干凈得多,沒有絲毫多余的東西:
gulp.task('usemin', function() {
gulp.src('./*.html')
.pipe(usemin({
css: [minifyCss(), 'concat'],
html: [minifyHtml({empty: true})],
js: [uglify(), rev()]
}))
.pipe(gulp.dest('build/'));
});
usemin
不需要有minifyCss
、minifyHtml
、uglify
和rev
這幾個插件的任何知識,只要把對應的內容從stream丟出去就好。在用這些插件組裝task時才需要關心。
當前,Gulp的社區還遠不如Grunt成熟,有些功能的插件,Gulp可能就沒有。這其實不算很大的劣勢,只要足夠好用,追上來很快。而且,寫一個Gulp插件要比相應的Grunt插件短小得多!