Webpack與Gulp、Grunt沒有什麼可比性,它能夠看做模塊打包機,經過分析你的項目結構,找到JavaScript模塊以及其它的一些瀏覽器不能直接運行的拓展語言(Scss,TypeScript等),並將其轉換和打包爲合適的格式供瀏覽器使用。Gulp/Grunt是一種可以優化前端的開發流程的工具,而WebPack是一種模塊化的解決方案,不過Webpack的優勢使得Webpack在不少場景下能夠替代Gulp/Grunt類的工具。html
他們的工做方式也有較大區別:前端
Grunt和Gulp的工做方式是:在一個配置文件中,指明對某些文件進行相似編譯,組合,壓縮等任務的具體步驟,工具以後能夠自動替你完成這些任務。java
Webpack的工做方式是:把你的項目當作一個總體,經過一個給定的主文件(如:index.js),Webpack將從這個文件開始找到你的項目的全部依賴文件,使用loaders處理它們,最後打包爲一個(或多個)瀏覽器可識別的JavaScript文件。node
grunt vs gulp
雖然gulp已經出來好久了,可是一直沒有去使用過。得益於最近項目須要,就嘗試了一下,如下從幾個要點講一下grunt和gulp使用的區別,側重講一下在使用gulp過程當中發現的問題。而兩種工具孰優孰劣由讀者本身判斷。jquery
1. 書寫方式
grunt 運用配置的思想來寫打包腳本,一切皆配置,因此會出現比較多的配置項,諸如option,src,dest等等。並且不一樣的插件可能會有本身擴展字段,致使認知成本的提升,運用的時候要搞懂各類插件的配置規則。
gulp 是用代碼方式來寫打包腳本,而且代碼採用流式的寫法,只抽象出了gulp.src, gulp.pipe, gulp.dest, gulp.watch 接口,運用至關簡單。經嘗試,使用gulp的代碼量能比grunt少一半左右。git
2. 任務劃分
grunt 中每一個任務對應一個最外層配置的key, 大任務能夠包含小任務,以一種樹形結構存在。舉個栗子:github
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至關有用。npm
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. 其餘要交代的
- gulp真的只有src, pipe, dest, watch, run這幾個API嗎? 不,因爲gulp繼承了Orchestrator(<4.0),因此具有了另一些API,包括start等。固然這些API是官方不推薦使用的。會致使代碼的複雜度提高,因此並無出如今官方文檔中。
- 不建議將多個操做寫在同個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); });
- 儘可能減小task的數量,不少任務其實能夠在一個task中用多個pipe來執行,只須要咱們在打包等時候規劃好文件夾及任務流。
對了,gulp4.0會帶給咱們不少驚喜(wtf!),雖然它仍是遲遲未發佈... 暫時不想去踩坑。讀者可自行Google。