1. Grunt -> Gulp
早些年提到構建工具,難免會讓人聯想到歷史比較悠久的Make
,Ant
,以及后來為了更方便的構建結構類似的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任務運行器。
2. Grunt的特點
- Grunt有一個完善的社區,插件豐富
- 它簡單易學,你可以隨便安裝插件並配置它們
- 你不需要多先進的理念,也不需要任何經驗
完善
– Grunt的插件數據: 根據社區的結果顯示,共計3,439個插件,其中49個官方插件。
易用
– Grunt的插件豐富: 許多常見的任務都有現成的Grunt插件,而且有眾多第三方插件,如:CoffeeScript
,Handlebars
,Jade
,JsHint
,Less
,RequireJS
,Sass
,Styles
。而且通過參考文檔進行配置便可以使用。
3. Gulp和Grunt的異同點
- 易於使用:采用代碼優於配置策略,Gulp讓簡單的事情繼續簡單,復雜的任務變得可管理。
- 高效:通過利用Node.js強大的流,不需要往磁盤寫中間文件,可以更快地完成構建。
- 高質量:Gulp嚴格的插件指導方針,確保插件簡單並且按你期望的方式工作。
- 易於學習:通過把API降到最少,你能在很短的時間內學會Gulp。構建工作就像你設想的一樣:是一系列流管道。
易用
Gulp相比Grunt更簡潔,而且遵循代碼優於配置策略,維護Gulp更像是寫代碼。
高效
Gulp相比Grunt更有設計感,核心設計基於Unix流的概念,通過管道連接,不需要寫中間文件。
高質量
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流程的不同
- 使用Grunt的I/O過程中會產生一些中間態的臨時文件,一些任務生成臨時文件,其它任務可能會基於臨時文件再做處理並生成最終的構建后文件。
- 而使用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對象有兩個主要的屬性:path
和content
。因為一個文件不僅可能是你硬盤上的一些內容,還可能是你托管在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中,解決上述任務依賴的方式有三種:
- 在任務定義的function中返回一個數據流,當該數據流的end事件觸發時,任務結束。
- 在任務定義的function中返回一個promise對象,當該promise對象resolve時,任務結束。
- 在任務定義的function中傳入callback變量,當callback()執行時,任務結束。
Gulp腳本中可以使用這三種方法來實現任務依賴,不過由於Gulp中的任務大多是數據流操作,因此以第一種方法為主。
12. Gulp的插件開發
所有的Gulp.js插件基本都是through
(后面不再使用transform這個詞)streams,即是消費者(接收gulp.src()
傳遞出來的數據,然后進行處理加工處理),又是生產者(將加工后的數據傳遞出去)。Gulp.js的使用和插件的開發都很簡單,當然里面還有很多細節,拋磚引玉,具體請看Gulp.js的官方文檔。