2015年7月12日 星期日

Gulp 介紹 - 更多應用 (二)


上一篇 [Gulp 介紹 - 更多應用] 介紹一些好用的 gulp 套件,這篇主要介紹如何撰寫出更好的 gulp 檔案。


1. 一定要記得 return gulp.src stream


剛開始學習的時候,網路上的教材有兩種寫法。

第一種:無 return
gulp.task("taskName", function() {
   gulp.src("檔案")...
}

第二種:有 return
gulp.task("taskName", function() {
   // 加了 return 在 gulp src 前面
   return gulp.src("檔案")...
}

第一種跟第二種的差別在於,有 return 的我們會知道 tasks 已經結束的時機,而前面沒加 return 的我們不會知道。

gulp 很聰明。在還沒到 concurrency 上限前,預設會同時跑所有需要的 tasks,並且不等待任何返回值。
(簡單來說。By default, gulp task is asynchronous task)

這時,當我們需要有順序的跑 tasks 時,就會因為完成順序不如預期出現錯誤。

範例,我們希望在跑 minify css 之前,先做 scss 編譯。
var sass = require('gulp-ruby-sass');
var minifyCSS = require('gulp-minify-css');

gulp.task('scss', function() {
    // syntax change gulp-ruby-sass starting from 1.0.0-alpha
   sass('./scss', { style: 'expanded' })
        .pipe(gulp.dest('./css/'));
});
// 期望 scss 跑完在跑 minify css, 
// ['scss'] 是指定 dependency,styles task 會在 scss 跑完後再跑
gulp.task('styles', ['scss'], function () {
    gulp.src('./css/**/*.css')
        .pipe(minifyCSS())
        .pipe(gulp.dest('./dist/css/'));
});
terminal 結果:
myBook:gulp-test yvonne$ gulp styles
[21:30:25] Using gulpfile ~/work/gulp-test/gulpfile.js
[21:30:25] Starting 'scss'...
[21:30:25] Finished 'scss' after 5.99 ms // 跑完 scss...
[21:30:25] Starting 'styles'...          // 開始跑 styles...
[21:30:25] Finished 'styles' after 4.11 ms 
[21:30:26] directory ./
[21:30:26] write ./main.css // 這裏才是 scss 真正編譯完成

雖然結果顯示 styles 是等 scss 跑完後才跑的。
可是後面的 log 卻寫說編譯的 css 是等全部結束後才寫入,結果根本非預期想的那樣。

解法非常簡單,只要加 return 回傳 stream 提示 tasks 結束即可

var sass = require('gulp-ruby-sass');
var minifyCSS = require('gulp-minify-css');

gulp.task('scss', function() {
   return sass('./scss', { style: 'expanded' })
        .pipe(gulp.dest('./css/'));
});

// 期望 scss 跑完在跑 minify css, 
// ['scss'] 是指定 dependency,styles task 會在 scss 跑完後再跑
gulp.task('styles', ['scss'], function () {
    return gulp.src('./css/**/*.css')
        .pipe(minifyCSS())
        .pipe(gulp.dest('./dist/css/'));
});
myBook:gulp-test yvonne$ gulp styles
[21:39:02] Using gulpfile ~/work/gulp-test/gulpfile.js
[21:39:02] Starting 'scss'...
[21:39:02] directory ./
[21:39:02] write ./main.css // scss 編譯完成!
[21:39:03] Finished 'scss' after 249 ms
[21:39:03] Starting 'styles'... // styles 才開始跑
[21:39:03] Finished 'styles' after 24 ms

為了避免類似的問題再發生,建議是 return 所有的 gulp stream。
只要沒有特定指定 dependency 的情況下, gulp tasks 都會是採非同步的方式跑。一樣效能很好!
ref:
http://stackoverflow.com/questions/21699146/gulp-js-task-return-on-src

2. 使用外部的 config 管理變數(like grunt)

在 gulp.js 的 github recipes 上有一則何使用 json 管理 gulp 變數,維持 tasks DRY(Don't Repeat Yourself)。

用法是把變數寫入在外部的 json 檔案,然後 tasks 可呼叫相同變數。開發者只需要維護 json 裡的變數即可,而不用維護分佈在不同 tasks 的變數。
雖然用 json 維護很方便,可是我個人喜歡用 node 產生 config module,讓變數可有繼承關係。

新增 gulp.config.js
module.exports = function () {
    var client = 'root/',
        dist = client + 'dist/';

    return {
        // root
        client: client,
        scss: {
            files: client + '/scss/**/*.scss',
            css: {
                distPath: dist + 'css/',
                name: 'main.css'
            }
        }
    };
};

在 gulpfile.js 記得把 config.js require 進來
var gulp = require('gulp'),
    // 需要啟用 config module 的 function 
    config = require('./gulp.config')();

// print 'root/'
console.log('client path:', config.client);


3. del 取代 rimraf

跟本次『撰寫出更好 gulp』主題無關,增加這點只因我們原本都使用 rimraf 套件去刪檔案。可是最近發現 rimraf 已經被 del 取代了。(Orz... 也太晚發現了)所以就私心的把這發現寫在第三點,然後順便介紹 del 的用法~(rimraf github issue 說明)

刪除檔案在 gulp task 是很常見的動作。最常使用方式是當我們需要重新編譯檔案時,先把原先編譯完的舊檔案刪除,避免發生未知的錯誤...
(例如,其實重新編譯的檔案沒有成功,可是因為舊的檔案存在,所以一直以為有成功。然後就 debug 一陣子...)
(或是例如有些套件有 cache 的問題...)

常見用法如下:(scripts 前先清除之前的 js 檔案 )
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var del = require('del');

// pass callback argument 非常重要!!
gulp.task('clean:js', function (cb) {
    // 刪除在 js folder 下面全部的檔案
    del(['dist/js/**.*'], cb);
});

gulp.task('scripts', ['clean:js'], function() {
    gulp.src('./js/**/*.js')
        .pipe(uglify())
        .pipe(concat('all.min.js'))
        .pipe(gulp.dest('./dist/js/'))
});

del 用法很簡單,就是把想要刪除的 path array 放在第一個 argument 即可。(支援 globbing patterns
然後別忘了要 pass callback,原因跟上面介紹的第一點『 return steam 』原理一樣。當有 dependency 的時候,我們需要讓 task 知道刪除結束的時間點。del 套件在刪除檔案時會呼叫 callback,通知 task dependency 已經結束了~ 這樣才不會刪除跟編譯同時進行喔!


1 則留言: