【譯】相對完整的Gulp4升級指南

原文連接:The Complete-Ish Guide to Upgrading to Gulp 4javascript

雖然Gulp4始終在開發中,可是你要堅信在未來的某一天你必定能夠等到它的正式版。嗯,某一天。因此如今我想先向大家介紹Gulp3.x和Gulp4之間的不一樣,同時但願可以幫助你未來能相對無痛的遷移到新的版本。java

安裝

在你開始使用最新版的Gulp以前,你必需要先檢查一下你Gulp的版本。一般,你只須要更新你的package.json中的版本號就好了,不過有時候你也有可能碰到一些額外的麻煩。最可能的緣由是你分別在項目文件夾下和全局環境中都安裝了Gulp(若是你讀過了這篇文章the practice of using npm scripts to access the locally installed version of CLI’s,那就好辦多了。雖然在這裏它可能仍是幫不了你太多)。所以,首先你要把你項目文件夾下的Gulp刪除,若是你在全局環境中也安裝了Gulp,最好也把它刪了。git

npm uninstall gulp --save-dev
npm uninstall gulp -g

如今你就能夠在你的項目中安裝Gulp4。因爲它尚未正式發佈,咱們只能直接經過Github來安裝它:github

npm install gulpjs/gulp.git#4.0  --save-dev

當它提交到npm庫以後,你就能夠像日常同樣使用npm install gulp --save-dev了。而且當它發佈正式版本後,咱們也最好不要從Github上安裝,改成直接從npm上進行安裝。好了,如今咱們還有另外一個東西須要安裝:命令行工具。跟如今的Grunt相似的,Gulp4把命令行工具從Gulp的核心代碼中剝離了。Gulp3和Gulp4都能使用獨立出來的命令行工具。npm

npm install gulp-cli --save-dev

若是你不想在你的項目中使用npm scripts,你須要使用-g替換-save-dev來進行全局安裝。如今你就能夠像之前同樣使用gulp命令了,可是你應該會獲得一個錯誤信息,由於你須要更新你的gulpfile.js來兼容最新版的Gulp。json

任務重構

若是你原來的任務代碼結構十分簡單,任務以前沒有相互的依賴。那很走運,你將不須要作任何修改!不過使人哀傷的是,大部分人都不得不作一些調整。Gulp4最大的一個改變就是gulp.task函數如今只支持兩個參數,分別是任務名和運行任務的函數。舉個例子,下面的任務代碼能夠很好的運行在Gulp3和Gulp4上面:gulp

gulp.task('clean', function() {...})

可是當你使用三個參數時該怎麼辦?咱們要如何指定任務之間的依賴關係?這時新的gulp.seriesgulp.parallel函數應該能幫助你解決難題。這兩個函數均可以接受數個函數或任務名做爲參數,通過組合後,返回一個新的函數。gulp.series會返回一個函數用來順序執行它所接受的任務/函數,而gulp.parallel返回的函數則會並行的運行它們。Gulp總算可以讓咱們自由的選擇以串行或並行的方式來執行任務而再也不須要其餘的第三方依賴(好比經常使用的run-sequence),也不用再定義一堆讓人看不懂的任務依賴。數組

若是你之前是這麼寫:promise

gulp.task('styles', ['clean'], function() {
    ...
});

那你如今能夠這樣:異步

gulp.task('styles', gulp.series('clean', function() {
    ...
}));

在改寫的時候,不要忘了其實如今你處理主要任務的函數也是放在gulp.series裏面調用,因此不要忘了在結尾加上括號。不少人常常犯這個錯誤。

注意,因爲gulp.seriesgulp.parallel返回的是一個函數,因此他們是能夠被嵌套調用的。若是您的任務每每有多個依賴任務,你會常常嵌套調用它們。好比這個例子:

gulp.task('default', ['scripts', 'styles'], function() {
    ...
});

你能夠改寫成:

gulp.task('default', gulp.series(gulp.parallel('scripts', 'styles'), function() {
    ...
}));

看過去,這樣代碼讀起來很是吃力。不過考慮到這樣會使你任務流程控制更加的靈活,這點犧牲也就無所謂了。固然我以爲你也能夠本身封裝一些helper/alias函數來優化的你的代碼,提升可讀性,但我應該不會這麼去作。

依賴陷阱

在Gulp3中,假設你設定幾個有相同依賴的任務,而後運行它們,Gulp會檢測出這些將要運行的任務的依賴是同樣的,而後只會運行一次依賴任務。然而如今咱們再也不顯式的指定任務之間的依賴,而是經過series和parallel函數來組合任務,這樣會致使那些本應該只運行一次的任務,變成屢次運行。Gulp4是沒法作出相應的區分的。因此咱們要改變咱們指定任務依賴的思路。

讓咱們看一下這個Gulp3的例子:

// default任務,須要依賴scripts和styles
gulp.task('default', ['scripts', 'styles'], function() {...});

// script折styles任務都依賴clean
gulp.task('styles', ['clean'], function() {...});
gulp.task('scripts', ['clean'], function() {...});

// clean任務用來清空目錄
gulp.task('clean', function() {...});

咱們注意到stylesscripts任務都依賴clean任務。當你運行default任務時,Gulp3會率先運行stylesscripts任務,又由於檢測到這兩個任務都有各自的依賴,因此須要優先運行它們的依賴任務,這時Gulp注意到這兩個任務都依賴於clean,因而Gulp3將確保在回到stylesscripts任務以前,clean任務會被執行且執行一次。這頗有用!但遺憾的是,咱們在新版本中將沒辦法運用這個特性。若是你在遷移到Gulp4的過程當中只像下面的例子同樣作了簡單的改變,clean任務將會被執行兩次:

gulp.task('clean', function() {...});
gulp.task('styles', gulp.series('clean', function() {...}));
gulp.task('scripts', gulp.series('clean', function() {...}));

gulp.task('default', gulp.parallel('scripts', 'styles'));

這是由於parallelseries不是用來解決依賴的;他們只是用來把多個任務合併成一個。因此咱們須要把共同依賴的任務抽離出來,而後用一個更大的串行任務來包裹它們,以此來模擬任務依賴關係:

友情提示:你最好不要在定義那些小任務以前就用它們來組合你的default任務。由於在你調用gulp.series("taskName")以前,你必須已經定義好了一個名爲"taskName"的任務。因此通常在Gulp4中,咱們會在代碼的最後才定義default,而在Gulp3中你能夠把它放在任何地方。

// 任務直接再也不有依賴
gulp.task('styles', function() {...});
gulp.task('scripts', function() {...});
gulp.task('clean', function() {...});

// default任務,須要依賴scripts和styles
gulp.task('default', gulp.series('clean', gulp.parallel('scripts', 'styles')));

若是照這麼寫,當你單獨運行stylesscripts任務時,clean任務就不會優先自動執行。不過這問題也不大,在以前單獨運行clean任務就能夠了,同樣能把scripts和styles的文件夾清空。又或者你能夠從新定義一下你的任務,隨你,我也不肯定怎樣會更好。

異步任務支持

若是你執行的是同步任務,在Gulp3中不須要寫任何其餘代碼,可是在Gulp4中就不能如此輕鬆了:如今也你必須運行done回調(這多是我最先發現的一個變化)。而後若是你執行的是異步任務,你則有三個選擇來確保Gulp可以檢測到你的任務真的完成了,方法以下:

1) 回調

你能夠在你的任務函數的參數中提供一個回調函數而且在你的任務完成後調用它:

var del = require('del');

gulp.task('clean', function(done) {
    del(['.build/'], done);
});

2) 流

你也能夠返回一個流,一般經過gulp.srcvinyl-source-stream這個庫來建立。這通常也是最經常使用的方式:

gulp.task('somename', function() {
    return gulp.src('client/**/*.js')
        .pipe(minify())
        .pipe(gulp.dest('build'));
});

3) Promise

Promise這個技術早已聲名鵲起並且在Node中已經有了完整的實現,因此這也會是一個頗有用的方式。你只須要返回一個promise對象,Gulp就能知道任務在何時完成。

var promisedDel = require('promised-del');

gulp.task('clean', function() {
    return promisedDel(['.build/']);
});

其餘的異步任務支持

感謝Gulp如今引入了async-done庫,在最新的版本中咱們有更多的方式來確認異步任務的完成。

4)子進程

你能夠在你的任務中建立一些子進程並返回!好比,你能夠把你的npm scripts放到Gulp中執行,這樣你就不須要爲你的package.json中加載了百萬條命令而煩惱。你也能夠經過這樣的封裝擺脫那些隨時可能過期的gulp插件。儘管這看上去像一個反模式,不過你仍是有不少能夠優化它們的方法。

var spawn = require('child_process').spawn;

gulp.task('clean', function() {
  return spawn('rm', ['-rf', path.join(__dirname, 'build')]);
});

5)RxJS observable

我沒用過RxJS,它好像挺小衆的。不過對於那些RxJS的死忠粉絲,他們會很高興能夠返回一個observable對象。

var Observable = require('rx').Observable;

gulp.task('sometask', function() {
    return Observable.return(42);
});

監聽

處理文件系統的監聽和響應的API也有了一點進步。以前的API中,在咱們傳入一個glob通配符和可選參數後,咱們能夠再指定一個任務數組或者一個回調函數用來處理事件數據。但是如今,任務隊列都是由serise或者parallel函數合併而成,這樣你就沒法用一個回調來區分這些任務,因此咱們取消了這種簡單監聽回調的方式。取而代之的是,gulp.watch將像以前同樣會返回一個的「觀察」對象,不過你能夠對它添加各類事件監聽:

// 舊版
gulp.watch('js/**/*.js', function(event) {
    console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

// 新版:
var watcher = gulp.watch('js/**/*.js' /* 你能夠在這裏傳一些參數或者函數 */);
watcher.on('all', function(event, path, stats) {
  console.log('File ' + path + ' was ' + event + ', running tasks...');
});

// 單個事件的監聽
watcher.on('change', function(path, stats) {
  console.log('File ' + path + ' was changed, running tasks...');
});

watcher.on('add', function(path) {
  console.log('File ' + path + ' was added, running tasks...');
});

watcher.on('unlink', function(path) {
  console.log('File ' + path + ' was removed, running tasks...');
});

正如所看到的,在allchange的事件處理中,你還能夠接受一個stats對象。stats對象只在他們可用的時候出現(我也不肯定他們何時可用何時不可用),不過你能夠設置alwaysStat選項的值爲true來讓它始終出現。Gulp使用了chokidar庫來實現這些東西,閱讀chokidar的文檔能讓你瞭解的更多,儘管chokidar並不支持在事件回調中指定第三個參數。

使用函數

因爲如今每一個任務基本上就是一個函數,不須要任何依賴或其餘的什麼,實際上他們也僅僅是須要一個任務運行器來確認異步任務什麼時候結束,咱們能夠把函數定義從gulp.task中獨立出來,而不只僅做爲一個簡單回調函數傳給gulp.task。舉個例子,這個代碼以前咱們在「依賴陷阱」那個章節的結論:

gulp.task('styles', function() {...});
gulp.task('scripts', function() {...});
gulp.task('clean', function() {...});

gulp.task('default', gulp.series('clean', gulp.parallel('scripts', 'styles')));

我把它變成:

// 只須要在`series` 和 `parallel` 中間引用函數名就能組成一個新任務
gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles)));

// 把單個任務變成一個函數
function styles() {...}
function scripts() {...}
function clean() {...}

這裏有幾點要注意的地方:
1.因爲js是有函數定義提高的,函數的定義能夠放在你定義default任務以後,不像以前說的,若是你要用一些小任務組成一個新任務的時候,你就必需要先定義那些小任務。這樣就使得你能夠在一開始就定義好實際要運行的任務,這樣別人閱讀起來也更方便一些,以避免別人還要在翻閱了一堆其餘任務代碼後,才能發現藏在最後的實際要運行的那些。
2.styles, scripts, 和 clean 如今都至關於「私有」任務,他們沒法經過gulp命令行來運行。
3.這樣就沒有那麼多匿名函數了。
4.也沒有那麼多被引號包裹住的「任務」名了,這樣意味着你能夠經過你的代碼編輯器/IDE幫你檢查拼寫錯誤,而不用在運行Gulp的時候才能發現錯誤。
5.即便把「任務」函數放在多個文件中定義,也能方便的把它們引用到同一個文件中,而後再經過gulp.task把它們變成實際可用的任務。
6.這些任務都是能夠獨立測試的(若是你要測試)而不須要gulp。

固然第2點也是能夠修改的,若是你但願它們是能夠被gulp命令行所執行的:

gulp.task(styles);

這樣你就能新建了一個能夠運行在命令行的「styles」任務。注意你可歷來沒有在代碼中定義過它的名字。gulp.task能夠很智能的把函數名轉成任務名。固然,匿名函數是不行的:Gulp會拋出一個錯誤當你想要把匿名函數指定成一個任務,卻沒有給它起一個新名字。

若是你想給函數起個別名,你能夠在函數的displayName屬性中指定它:

function styles(){...}
styles.displayName = "pseudoStyles";
gulp.task(styles);

如今任務名將會從「styles」變成「pseudoStyles」。你也能夠經過指定description屬性來給你的任務添加描述。你能夠經過gulp --tasks命令來查看這些描述:

function styles(){...}
styles.displayName = "pseudoStyles";
styles.description = "Does something with the stylesheets."
gulp.task(styles);
$ gulp --tasks
[12:00:00] Tasks for ~/project/gulpfile.js
[12:00:00] └── pseudoStyles  Does something with the stylesheets.

你甚至能夠給你其餘已經註冊的任務添加描述,好比default。首先你要運行gulp.task('taskName')來取人這個任務已經被定義過了,而後纔給它添加描述:

gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles)));

// Use gulp.task to retrieve the task
var defaultTask = gulp.task('default');
// give it a description
defaultTask.description = "Does Default Stuff";

咱們也能夠簡化它,取消中間值:

gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles)));
gulp.task('default').description = "Does Default Stuff";

對那些不熟悉你的項目的人來講,這些描述是至關有用的。因此我建議在任何狀況下都要添加它:有時它比註釋還更有用。最後總結一下,這是我推薦的Gulp4的最佳實踐:

gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles)));
gulp.task('default').description = "This is the default task and it does certain things";

function styles() {...}
function scripts() {...}
function clean() {...}

若是你運行gulp --tasks,你將會看到:

$ gulp --tasks
[12:00:00] Tasks for ~\localhost\gulp4test\gulpfile.js
[12:00:00] └─┬ default  This is the default task and it does certain things
[12:00:00]   └─┬ <series>
[12:00:00]     ├── clean
[12:00:00]     └─┬ <parallel>
[12:00:00]       ├── scripts
[12:00:00]       └── styles

你會發現這裏不只有你添加的描述,你還能看到完整的運行隊列樹。我也很樂意聽到你對最佳實踐有其餘見解,不過在闡述結論前最好先跟你的團隊討論一下。

無論怎麼樣,我仍是很高興看到Gulp4有不少有用的改進,可是它們也給遷移帶來了很多痛苦。我但願這份指南能幫助你順利遷移到Gulp4當它正式發佈後(可能過幾天……也可能……)。上帝保佑~

相關文章
相關標籤/搜索