Grunt vs Gulp


grunt vs gulp

雖然gulp已經出來很久了,但是一直沒有去使用過。得益於最近項目需要,就嘗試了一下,以下從幾個要點講一下grunt和gulp使用的區別,側重講一下在使用gulp過程中發現的問題。而兩種工具孰優孰劣由讀者自己判斷。

1. 書寫方式

grunt 運用配置的思想來寫打包腳本,一切皆配置,所以會出現比較多的配置項,諸如option,src,dest等等。而且不同的插件可能會有自己擴展字段,導致認知成本的提高,運用的時候要搞懂各種插件的配置規則。
gulp 是用代碼方式來寫打包腳本,並且代碼采用流式的寫法,只抽象出了gulp.src, gulp.pipe, gulp.dest, gulp.watch 接口,運用相當簡單。經嘗試,使用gulp的代碼量能比grunt少一半左右。


2. 任務划分

grunt 中每個任務對應一個最外層配置的key, 大任務可以包含小任務,以一種樹形結構存在。舉個栗子:

uglify: {
    one: { src: 'src/a.js', dest: 'dest/a.min.js' }, two: { src: 'tmp/b.js', dest: 'dist/b.min.js' } }

將uglify划分子任務的好處是,我們在封裝不同的task時可以分別對'uglify:one'或'uglify:two'進行調用,這對於某些需要在不同時間點調用到uglify的task相當有用。

gulp 中沒有子任務的概念,對於上面的需求,只能通過注冊兩個task來完成

gulp.task('uglify:one', function(){ gulp.src('src/a.js') .pipe(uglify()) .dest('dest/a.min.js') }); gulp.task('uglify:two', function(){ gulp.src('tmp/b.js') .pipe(uglify()) .dest('dist/b.min.js') });

當然這種需求往往可以通過調整打包策略來優化,並不需要分解子task,特殊情況下可以用這種方法解決。

3. 運行效率

grunt 采用串行的方式執行任務,比如我們注冊了這樣一個任務:
grunt.register('default', ['concat', 'uglify', 'release'])
grunt是按書寫的順序首先執行cancat,然后是uglify,最后才是release,一派和諧的氣氛,誰也不招惹誰。而我們知道某些操作時可以同步執行的,比如cssmin和uglifyjs。這時grunt無法通過簡單地更改配置來達到並行執行的效果,通常的做法是手動寫異步task,舉個栗子:

grunt.registerTask('cssmin', 'async cssmin task', function() { var done = this.async(); cssmin(done); });

在cssmin操作完成后傳入done方法告知程序,但這需要插件支持。

gulp 基於並行執行任務的思想,通過一個pipe方法,以數據流的方式處理打包任務,我們來看這段代碼:

gulp.task('jsmin', function () { gulp.src(['build/js/**/*.js']) .pipe(concat('app.min.js')) .pipe(uglify() .pipe(gulp.dest('dist/js/')); });

程序首先將build/js下的js文件壓縮為app.min.js, 再進行uglify操作,最后放置於dist/js下。這一系列工作就在一個task中完成,中間沒有產生任何臨時文件。如果用grunt,我們需要怎樣寫這個任務?那必須是有兩個task配置,一個concat,一個uglify,中間還必須產生一個臨時文件。從這個角度來說,gulp快在中間文件的產生只生成於內存,不會產生多余的io操作。
再來看看前面的問題,如何並行執行uglify和cssmin?其實gulp本身就是並發執行的,我們並不需要多什么多余多工作,只需

gulp.task('default', ['uglify', 'cssmin']);

gulp該怎么快就怎么來,並不會等到uglify再執行cssmin。
是不是覺得gulp秒殺grunt幾條街了呢?且慢,坑還在后面...
首先我們需要問一個問題,為什么要用並發?
為了快?那什么時候可以快,什么時候又不能快?
假設我們有這樣一個任務:

gulp.task('jsmin', ['clean', 'concat']);

需要先將文件夾清空,再進行合並壓縮,根據gulp的並發執行的方式,兩個任務會同時執行,雖然從指令上看是先執行了clean再執行concat,然而clean還沒結束,concat就執行了,導致代碼合並了一些未被清理的文件,這顯然不是我們想要的結果。
那這個問題有沒有什么解決方案呢?
gulp官方API給出了這樣的方法:

  • 給出一個提示,來告知 task 什么時候執行完畢
  • 並且再給出一個提示,來告知一個 task 依賴另一個 task 的完成

官方舉了這個例子:
讓我們先假定你有兩個 task,"one" 和 "two",並且你希望它們按照這個順序執行:
1. 在 "one" 中,你加入一個提示,來告知什么時候它會完成:可以再完成時候返回一個 callback,或者返回一個 promise 或 stream,這樣系統會去等待它完成。
2. 在 "two" 中,你需要添加一個提示來告訴系統它需要依賴第一個 task 完成。

因此,這個例子的實際代碼將會是這樣:

var gulp = require('gulp'); // 返回一個 callback,因此系統可以知道它什么時候完成 gulp.task('one', function(cb) { // 做一些事 -- 異步的或者其他的 cb(err); // 如果 err 不是 null 或 undefined,則會停止執行,且注意,這樣代表執行失敗了 }); // 定義一個所依賴的 task 必須在這個 task 執行之前完成 gulp.task('two', ['one'], function() { // 'one' 完成后 }); gulp.task('default', ['one', 'two']);

task one執行完畢后需要調用cb方法來告知task two我已經執行完成了,你可以干你的事了。
那在我們實際運用中,通常是這樣的:

gulp.task('clean', function (cb) { gulp.src(['tmp']) .pipe(clean()); });

這個時候clean結束的cb要寫在哪呢?是這樣嗎?

gulp.task('clean', function (cb) { gulp.src(['tmp']) .pipe(clean()); cb(); });

對於理解什么叫異步的人來說這種方法肯定是不行的,clean還沒完成,cb已經執行了。好在!!!
好在我們可以利用gulp中的時間監聽來做結束判斷:

gulp.task('clean', function (cb) { gulp.src(['tmp']) .pipe(clean()), .on('end', cb); }); gulp.task('concat', [clean], function(){ gulp.src('blabla') .pipe('blabla') .dest('blabla'); });

由於gulp是用node實現的,所以必然綁定了數據流的監聽事件,我們通過監聽stream event end來達到這個目的。
而不得不吐槽的是通過在task后面寫[]依賴的方式也並不優雅,通常可以通過其他插件來達到順序執行的效果,寫法如同grunt,但是每個task的end事件的監聽也是少不了的。
如果你的任務不多的時候,直接在回調后面執行concat也是可以的:

gulp.task('clean', function(){}) gulp.task('concat', function(){}) gulp.task('clean-concat', ['clean'], function(){ gulp.start('concat'); })

4. 其他要交代的

  1. gulp真的只有src, pipe, dest, watch, run這幾個API嗎? 不,由於gulp繼承了Orchestrator(<4.0),所以具備了另外一些API,包括start等。當然這些API是官方不推薦使用的。會導致代碼的復雜度提升,所以並沒有出現在官方文檔中。
  2. 不建議將多個操作寫在同個task中,這樣程序並不知道任務及時結束,如:
gulp.task('test', function(cb) { gulp.src('bootstrap/js/*.js') .pipe(gulp.dest('public/bootstrap')) .on('end', cb); gulp.src('jquery.cookie/jquery.cookie.js') .pipe(gulp.dest('public/jquery')) .on('end', cb); });
  1. 盡量減少task的數量,很多任務其實可以在一個task中用多個pipe來執行,只需要我們在打包等時候規划好文件夾及任務流。

對了,gulp4.0會帶給我們很多驚喜(wtf!),雖然它還是遲遲未發布... 暫時不想去踩坑。讀者可自行Google。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM