gulp的關鍵在於流,這從它的logo就能看出來。javascript
在node中,流是操做文件時一個重要的概念。流是指什麼呢?它包含兩個含義:「水流」和「流水」。 水流蘊含了源源不斷或是一股一股那樣流過的意味;而流水是「流水線」或是「流水做業」裏那種讓物件經過各個環節依次對其加工的意思。 咱們常常接觸到的「流媒體」主要是前者的含義,當你在線看一部電影時,影音數據從服務器源源不斷地流入你的播放器, 再通過一些處理展示在你眼前;而gulp中的流我以爲含義偏重於後者,由於gulp的任務就是把源文件進行各類加工處理最終輸出到指定位置。 咱們說「源文件」而不是「原文件」,在gulp中,它還真是流的源頭。java
gulp是基於node的,可是它並無直接使用node中fs模塊裏的文件系統和流,而是包裝了一層vinyl。 vinyl是一個用來描述文件的簡單的數據格式,經過vinyl-fs能夠把node原生的文件系統封裝成vinyl。 這個封裝使得整個流的過程更加簡單。從源頭上,vinyl使用glob語法獲取源,好比經過一個表達式 "src/**/*.js"就得到到了src目錄下各級目錄中的js文件,這要是用原生的fs恐怕得寫個遍歷樹的算法程序了吧。 在gulp或vinyl-fs的api裏,經過一個傳入glob表達式的src方法就得到到了一個流的源。 很明顯,在多數狀況下這個源是由多個文件組成的,能夠想象成這些文件構成了一個一股一股的文件流, 都將要經過一系列管道被加工處理。那麼接下來就是管道,與原生的fs相同,vinyl使用管道也是用pipe方法。 pipe接受一個函數爲參數,將當前流的內容傳給這個函數讓其加工,vinyl把流的內容封裝得更加簡明好用, 並且,對於調用一次pipe方法,其傳入的函數會對這個流的全部文件做用,換句話說,傳入pipe方法的函數其實是針對一個文件的, 而流中全部的文件都會被這個函數加工一下。這麼看,vinyl的流有些並行的感受,但本質上說javascript是單線程的, 加工的過程仍是一個接着一個進行的,因此說成讓文件一個接一個地流過某個管道更確切。node
既然是流,就應該有一種順序進行的感受。不過處理流的代碼是異步的,好比下面的代碼:算法
gulp = require('gulp') through = require('through2') gulp.task 'test', -> stream = gulp.src('src/js/*.js') .pipe through.obj (file, enc, cb) -> console.log 'processing...' cb null, file .pipe(gulp.dest('test')) console.log 'end'
若是在src/js目錄下有兩個js文件。執行gulp task,結果是:編程
[20:12:32] Starting 'test'... end [20:12:32] Finished 'test' after 13 ms processing... processing...
很顯然,pipe中的函數是異步執行的。不過對於流中的一個文件,各pipe中的函數必定會按照前後順序執行。 再來看一段代碼,爲了方便,我把管道中的處理函數寫成通常gulp插件的形式:gulp
processor = (info) -> through.obj (file, enc, cb) -> console.log file.path, info cb null, file gulp.task 'test', -> stream = gulp.src('src/js/*.js') .pipe processor("in pipe 1") .pipe processor("in pipe 2") .pipe processor("in pipe 3") .pipe(gulp.dest('test')) console.log 'end'
執行結果是:api
[16:31:24] Starting 'test'... end [16:31:24] Finished 'test' after 9.02 ms /src/js/city.js in pipe 1 /src/js/city.js in pipe 2 /src/js/city.js in pipe 3 /src/js/sysUtils.js in pipe 1 /src/js/sysUtils.js in pipe 2 /src/js/sysUtils.js in pipe 3
執行結果的確是像流那樣一個文件挨着一個文件,一個過程接着一個過程處理完成的。儘管pipe中的函數會異步執行, 但它們嚴格按照先註冊先執行的順序進行。看來一個任務的執行順序在一個流中是可以得以保證的,並且也只能在一個流中得以保證。數組
那麼對於多個任務的狀況呢?gulp.task方法能夠接受一個任務數組,任務數組中的任務將會並行執行。 而傳入gulp.task的函數將會在任務數組中全部任務執行完畢後開始執行。簡單來講是這樣的,實際要當心。promise
gulp的api中關於task方法有這麼一項注意:「Are your tasks running before the dependencies are complete? Make sure your dependency tasks are correctly using the async run hints: take in a callback or return a promise or event stream.」 中文版本是:「你的任務是否在這些前置依賴的任務完成以前運行了?請必定要確保你所依賴的任務列表中的任務都使用了正確的異步執行方式:使用一個 callback,或者返回一個 promise 或 stream。」服務器
既然任務中的那些處理函數通常都是異步執行的,那麼怎麼才能知道它們執行完了呢?只能是經過回調了, 能夠是直接的回調,也能夠是其它約定好的回調,也就是promise或者流的事件。 對於JavaScript異步編程來講這是很常見的事情,然而這也帶來了相應的侷限性。來看個例子:
gulp.task 'buildjs', (cb) -> del.sync('prd/js') gulp.src('src/js/**/*.js') .pipe uglify({output:{ascii_only:true}}) .pipe gulp.dest('prd/js') gulp.src(['src/js/**/*.*', '!src/js/**/*.js']) .pipe gulp.dest('prd/js')
這是一個很常見的任務,就是把js代碼混淆壓縮,而後把非js代碼原樣拷貝出去。我寫的是coffee版的gulpfile, coffeescript會默認把最後一個表達式做爲返回值,因此這裏其實是返回了第二個流,也就是拷貝非js文件的那個流。 若是隻執行這個任務倒無所謂,誰先誰後都能完成,可是若是它被做爲前置任務呢?
gulp.task zip, ['buildjs'], -> gulp.src('prd/js/*.js') .pipe(zip('release.zip')) .pipe(gulp.dest('prd'))
若是文件比較多的話,會發現壓縮包裏的文件不完整。緣由就是buildjs這個任務返回的流是拷貝文件那個流, 而zip這個任務也只會等待拷貝文件完成時開始,此時混淆文件那個流還不必定能執行完。若是返回混淆文件那個流, 照常理說這個流會執行的慢一些,但仍不那麼靠譜,畢竟沒有邏輯保障,因此應該把它們都拆開,分別做爲zip的前置任務:
gulp.task 'buildjs', -> gulp.src('src/js/**/*.js') .pipe uglify({output:{ascii_only:true}}) .pipe gulp.dest('prd/js') gulp.task 'copy', -> gulp.src(['src/js/**/*.*', '!src/js/**/*.js']) .pipe gulp.dest('prd/js') gulp.task 'zip', ['buildjs', 'copy'], -> gulp.src('prd/js/*.js') .pipe(zip('release.zip')) .pipe(gulp.dest('prd'))
這樣,zip必定會等buildjs和copy兩個任務中各自的流全都執行完纔會開始執行,從邏輯上也沒問題了。 這樣看好像是把原本能夠在一個任務裏完成的東西拆開了,不過gulp自己鼓勵短小專注,全部的gulp插件都很小, 且只完成一件事情。這麼說的話構建js文件和拷貝非js文件說是兩件事也比較合理。
上例是一個兩級的順序保障,「分-總」的結構。你也許發現我偷偷地把刪除目錄的一句給去掉了。由於個人確不知道該把它放在哪裏好。 buildjs和copy是並行的,不能確保誰先,若是放到buildjs裏,萬一copy先執行了,誤刪了已經拷貝過去的東西可很差。 因此,我須要多級的順序保障,把刪除目錄的任務放在更高的一級,造成一個「總-分-總的結構」。 然而我並沒找到造成這種結構的方法,貌似只能一個接着一個地進行:
gulp.task 'clean', -> del.sync('prd/js') gulp.task 'copy', ['clean'], -> gulp.src(['src/js/**/*.*', '!src/js/**/*.js']) .pipe gulp.dest('prd/js') gulp.task 'buildjs', ['copy'], -> gulp.src('src/js/**/*.js') .pipe uglify({output:{ascii_only:true}}) .pipe gulp.dest('prd/js') gulp.task 'zip', ['buildjs'], -> gulp.src('prd/js/*.js') .pipe(zip('release.zip')) .pipe(gulp.dest('prd'))
這樣順序是沒啥問題了,就是看着挺彆扭的,我須要一個構建js文件夾裏面內容的任務,卻須要一層又一層地依賴多個任務。 還有一種辦法能夠把全部步驟一股腦地放在一個任務裏,就是利用流的事件,vinyl的流和原生fs流其實基本同樣, 也有那些事件,因此能夠利用end事件來控制順序。我認爲上面的copy和buildjs兩個任務不該當拆開,就把他們寫在一塊兒:
gulp.task 'copy', ['clean'], (cb) -> lastStream = null gulp.src(['src/js/**/*.*', '!src/js/**/*.js']) .pipe gulp.dest('prd/js'). .on 'end', -> lastStream = gulp.src('src/js/**/*.js') .pipe uglify({output:{ascii_only:true}}) .pipe gulp.dest('prd/js') .on 'end', cb
要注意的是,下一個任務須要等待這個任務最有一個流執行完再開始,因此這裏須要在最後執行的流上加上對end事件的處理,執行參數傳入的回調。