Node開發項目管理工具 Grunt 對比 Gulp

轉自Gulp vs Gruntjavascript

1. Grunt -> Gulp

早些年提到構建工具,不免會讓人聯想到歷史比較悠久的MakeAnt,以及後來爲了更方便的構建結構相似的Java項目而出現的Maven。Node催生了一批自動化工具,像Bower,Yeoman,Grunt等。而現在前端提到構建工具會天然想起Grunt。Java世界裏的Maven提供了強大的包依賴管理和構建生命週期管理。前端

在JavaScript的世界裏,Grunt.js是基於Node.js的自動化任務運行器。2013年02月18日,Grunt v0.4.0 發佈。Fractal公司積極參與了數個流行Node.js模塊的開發,它去年發佈了一個新的構建系統Gulp,但願可以取其精華,並取代Grunt,成爲最流行的JavaScript任務運行器。java

2. Grunt的特色

  • Grunt有一個完善的社區,插件豐富
  • 它簡單易學,你能夠隨便安裝插件並配置它們
  • 你不須要多先進的理念,也不須要任何經驗

完善 – Grunt的插件數據: 根據社區的結果顯示,共計3,439個插件,其中49個官方插件。node

易用 – Grunt的插件豐富: 許多常見的任務都有現成的Grunt插件,並且有衆多第三方插件,如:CoffeeScriptHandlebarsJadeJsHintLessRequireJSSassStyles。並且經過參考文檔進行配置即可以使用。nginx

3. Gulp和Grunt的異同點

  • 易於使用:採用代碼優於配置策略,Gulp讓簡單的事情繼續簡單,複雜的任務變得可管理。
  • 高效:經過利用Node.js強大的流,不須要往磁盤寫中間文件,能夠更快地完成構建。
  • 高質量:Gulp嚴格的插件指導方針,確保插件簡單而且按你指望的方式工做。
  • 易於學習:經過把API降到最少,你能在很短的時間內學會Gulp。構建工做就像你設想的同樣:是一系列流管道。

易用 Gulp相比Grunt更簡潔,並且遵循代碼優於配置策略,維護Gulp更像是寫代碼。gulp

高效 Gulp相比Grunt更有設計感,核心設計基於Unix流的概念,經過管道鏈接,不須要寫中間文件。promise

高質量 Gulp的每一個插件只完成一個功能,這也是Unix的設計原則之一,各個功能經過流進行整合並完成複雜的任務。例如:Grunt的imagemin插件不只壓縮圖片,同時還包括緩存功能。他表示,在Gulp中,緩存是另外一個插件,能夠被別的插件使用,這樣就促進了插件的可重用性。目前官方列出的有673個插件。瀏覽器

易學 Gulp的核心API只有5個,掌握了5個API就學會了Gulp,以後即可以經過管道流組合本身想要的任務。緩存

4. Yeoman Team Talks

Yeoman團隊去年12月份時在Github上也專門提過一個issue,討論是否使用Gulp取代Grunt:他們提到Gulp是一個新的基於流的管道式構建系統,須要不多的配置而且更快。網絡

5. Gruntfile.js

module.exports = function(grunt) {
grunt.initConfig({
    concat: {
        'dist/all.js': ['src/*.js']
    },
    uglify: {
        'dist/all.min.js': ['dist/all.js']
    },
    jshint: {
        files: ['gruntfile.js', 'src/*.js']
    },
    watch: {
        files: ['gruntfile.js', 'src/*.js'],
        tasks: ['jshint', 'concat', 'uglify']
    }
});


// Load Our Plugins
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');


// Register Default Task
grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
};

6. Gulpfile.js

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');

// Lint JS
gulp.task('lint', function() {
return gulp.src('src/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'));
});

// Concat & Minify JS
gulp.task('minify', function(){
return gulp.src('src/*.js')
    .pipe(concat('all.js'))
    .pipe(gulp.dest('dist'))
    .pipe(rename('all.min.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist'));
});

// Watch Our Files
gulp.task('watch', function() {
gulp.watch('src/*.js', ['lint', 'minify']);
});

// Default
gulp.task('default', ['lint', 'minify', 'watch']);

7. 差別和不一樣

  • 流:Gulp是一個基於流的構建系統,使用代碼優於配置的策略。
  • 插件:Gulp的插件更純粹,單一的功能,並堅持一個插件只作一件事。
  • 代碼優於配置:維護Gulp更像是寫代碼,並且Gulp遵循CommonJS規範,所以跟寫Node程序沒有差異。
  • 沒有產生中間文件

8. I/O流程的不一樣

  1. 使用Grunt的I/O過程當中會產生一些中間態的臨時文件,一些任務生成臨時文件,其它任務可能會基於臨時文件再作處理並生成最終的構建後文件。
  2. 而使用Gulp的優點就是利用流的方式進行文件的處理,經過管道將多個任務和操做鏈接起來,所以只有一次I/O的過程,流程更清晰,更純粹。

9. Gulp的核心:流

Gulp經過流和代碼優於配置策略來儘可能簡化任務編寫的工做。這看起來有點「像jQuery」的方法,把動做串起來建立構建任務。早在Unix的初期,流就已經存在了。流在Node.js生態系統中也扮演了重要的角色,相似於*nix將幾乎全部設備抽象爲文件同樣,Node將幾乎全部IO操做都抽象成了Stream的操做。所以用Gulp編寫任務也可看做是用Node.js編寫任務。當使用流時,Gulp去除了中間文件,只將最後的輸出寫入磁盤,整個過程所以變得更快。

Doug McIlroy, then head of the Bell Labs CSRC (Computing Sciences Research Center), and inventor of the Unix pipe, summarized the Unix philosophy as follows:

This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.

基於流的模塊特色:

  • Write modules that do one thing and do it well.
  • Write modules that work together.
  • Write modules that handle events and streams.

Unix管道示例:

tput setaf 88 ; whoami | figlet | tr _ … | tr \ \` | tr \| ¡ | tr / √

10. 爲何應該使用流?

Node中的I/O操做是異步的,所以磁盤的讀寫和網絡操做都須要傳遞迴調函數。

var http = require('http');
var fs = require('fs');

var server = http.createServer(function (req, res) {
        fs.readFile(__dirname + '/data.txt', function (err, data) {
                res.end(data);
        });
});
server.listen(8000);

這個Node.js應用很簡單,估計全部學習過Node的人都作過這樣的練習,能夠說是Node的Hello World了。這段代碼沒有任何問題,你使用node能夠正常的運行起來,使用瀏覽器或者其餘的http客戶端均可以正常的訪問運行程序主機的8000端口讀取主機上的data.txt文件。可是這種方式隱含了一個潛在的問題,node會把整個data.txt文件都緩存到內存中以便響應客戶端的請求(request),隨着客戶端請求的增長內存的消耗將是很是驚人的,並且客戶端須要等待很長傳輸時間才能獲得結果。讓咱們再看一看另一種方式,使用流:

var http = require('http');
var fs = require('fs');

var server = http.createServer(function (req, res) {
        var stream = fs.createReadStream(__dirname + '/data.txt');
        stream.pipe(res);
});
server.listen(8000);

這裏面有一個很是大的變化就是使用createReadStream這個fs的方法建立了stream這個變量,並由這個變量的pip方法來響應客戶端的請求。使用stream這個變量就可讓node讀取data.txt必定量的時候就開始向客戶端發送響應的內容,而無需服務緩存以及客戶端的等待。

Node中Stream的種類

  • Readable(可讀)
  • Writeable(可寫)
  • Duplex(雙工)
  • Transform(運算雙工)

流能夠是可讀(Readable)或可寫(Writable),或者兼具二者(Duplex,雙工)的。全部流都是 EventEmitter,但它們也具備其它自定義方法和屬性,取決於它們是 Readable、Writable 或 Duplex。

Depends on

  • Liftoff
  • Through2
  • Vinyl, Vinyl-fs
  • Orchestrator

Liftoff模塊解決的問題是全局安裝一個CLI工具,但支持多個項目多個配置文件,而且當前目錄沒有配置文件時,能夠就近向上級目錄找到已有的配置文件,或者在項目目錄外執行命令行時能夠指定配置文件的目錄。因此Gulp基於liftoff能夠實現,多個項目多個Gulpfile,而且能夠執行gulp時指定配置文件路徑。

Through2是爲Node的streams2.Transform的小型封裝,來避免subclassing的煩惱。能夠更簡單的經過一個函數來建立一個流,而不用再繁瑣的設置原型鏈的_transform_flush,以及再擴充的Transform類中調用構造函數,以便緩衝設定可以正確初始化。

Vinyl是用來描述文件的一個很是簡單的元信息對象,Vinyl對象有兩個主要的屬性:pathcontent。由於一個文件不只多是你硬盤上的一些內容,還多是你託管在S3,FTP,甚至DropBox上的一些內容,因此Vinyl能夠描述全部這些來源的文件。它提供了一種簡潔的描述文件的方式,但若是你須要訪問本地文件系統上的一個文件,還須要經過一個所謂的Vinyl Adapter,它會暴漏一些方法:如.src(globs).dest(folder),和watch(globs, fn)。globs是路徑模式匹配。

Orchestrator實際上是一個基於Node的模塊,負責任務依賴關係定義,處理和執行,很像咱們目前所用的AMD模塊加載器,並且默認是最大限度的並行加載的方式。

事實上,gulp中的任務運行系統並非本身實現的,而是直接使用了orchestrator。在gulp的源代碼中能夠發現,gulp繼承了orchestrator,而gulp.task僅僅只是orchestrator.add的別名而已:

//gulp source code
var util = require('util');
var Orchestrator = require('orchestrator');

function Gulp() {
  Orchestrator.call(this);
}
util.inherits(Gulp, Orchestrator);

Gulp.prototype.task = Gulp.prototype.add;

11. Gulp的API

  • gulp.task
  • gulp.run
  • gulp.watch
  • gulp.src
  • gulp.dest

gulp.task

在orchestrator中,解決上述任務依賴的方式有三種:

  1. 在任務定義的function中返回一個數據流,當該數據流的end事件觸發時,任務結束。
  2. 在任務定義的function中返回一個promise對象,當該promise對象resolve時,任務結束。
  3. 在任務定義的function中傳入callback變量,當callback()執行時,任務結束。

Gulp腳本中可使用這三種方法來實現任務依賴,不過因爲Gulp中的任務大可能是數據流操做,所以以第一種方法爲主。

12. Gulp的插件開發

全部的Gulp.js插件基本都是through(後面再也不使用transform這個詞)streams,便是消費者(接收gulp.src()傳遞出來的數據,而後進行處理加工處理),又是生產者(將加工後的數據傳遞出去)。Gulp.js的使用和插件的開發都很簡單,固然裏面還有不少細節,拋磚引玉,具體請看Gulp.js的官方文檔。

相關文章
相關標籤/搜索