/ JavaScript

gulp 入門指南

Original article: Getting started with gulp by Mark Goodyear. This is a translated version in Traditional Chinese.

*


2014年1月28日更新,反映 gulp 的進步
2014年4月21日更新,使用更新的 gulp-livereload
2014年8月11日更新,使用 del 取代原有的 gulp-clean,更新 gulp-livereload
2015年5月20日更新,更新 gulp-ruby-sass 語句
2015年11月2日更新,更新 del 語句
2015年1月16日更新,捨棄 gulp-minify-css,改用gulp-cssnano

*

讓一讓 Grunt,有個新的建構工具來了。Gulp 是一種直覺的、code-over-configuration、串流的建構系統,它非常的快。

*為什麼我該感到興趣?*好問題。Gulp 的 code-over-configuration 不只容易撰寫,也更容易閱讀及維護。

Glup 使用 node.js 的串流(streams),讓建構更加快速,你再也不需要寫到磁碟暫存檔或目錄。如果你想更了解有關串流 -- 雖然不是必要的 -- 你可以閱讀這篇文章。Gulp 讓你用一個或多個來源檔案作為輸入(input),串流(pipe)這些檔案經過一些外掛(plugins),最後獲得輸出的結果,而非逐一配置每個外掛的輸入與輸出 -- 就像 Grunt。讓我們來看看分別在 Gulp 及 Grunt 基本建構 Sass 的示範:

Grunt:

sass: {
  dist: {
    options: {
      style: 'expanded'
    },
    files: {
      'dist/assets/css/main.css': 'src/styles/main.scss',
    }
  }
},

autoprefixer: {
  dist: {
    options: {
      browsers: [
        'last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'
      ]
    },
    src: 'dist/assets/css/main.css',
    dest: 'dist/assets/css/main.css'
  }
},

grunt.registerTask('styles', ['sass', 'autoprefixer']);

Grunt 需要配置各個外掛,指定來源與目的路徑。例如,我們將一個檔案作為 Sass 外掛的輸入,並儲存其輸出結果。同樣在設置 Autoprefixer 裡,需要將 Sass 的輸出結果作為輸入,產生出另一個檔案。來看看同樣在 Gulp 裡的配置:

Gulp:

gulp.task('sass', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
});

Gulp 我們只需要輸入一個檔案即可。經過 Sass 外掛的處理,再串流到 Autoprefixer 外掛,取得最終的檔案。這樣的流程可加快建構程序,省去不必要的檔案讀取及寫出,只需要最終的一個檔案。

所以,你有興趣了,接下來呢?讓我們來安裝 gulp 並且建立一個基本的 gulpfile,包含幾個核心任務來作為開始吧。

安裝 gulp

在我們鑽研設置任務之前,需要先安裝 gulp:

$ npm install gulp -g

這會將 gulp 安裝到全域環境下,讓你可以存取 gulp 的 CLI 。接著,我們需要安裝在本地端專案。cd 切換到你的專案根目錄,執行下述指令(請先確定你已有 package.json 檔):

$ npm install gulp --save-dev

上述指令將 gulp 安裝到本地端的專案內,並紀錄於 package.json 內的devDependencies 屬性。

安裝 gulp 外掛

接著安裝一些外掛,以便執行下列任務:

執行下列指令來安裝這些外掛:

$ npm install gulp-ruby-sass gulp-autoprefixer gulp-cssnano gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev

指令將會安裝必要的外掛,並紀錄於 package.json 內的 devDependencies 屬性。完整的 gulp 外掛清單可以在這裡找到。

載入外掛

接下來,我們需要建立一個 gulpfile.js 檔案,並且載入這些外掛:

var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    cssnano = require('gulp-cssnano'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

呼!看起來比 Grunt 有更多的事要做,對吧?Gulp 外掛跟 Grunt 外掛有些許差異 -- 它被設計成只做一件事並且做好這一件事。例如;Grunt 的 imagemin 利用快取來避免重複壓縮已經壓縮好的圖片。在 Gulp 中,這必須透過一個快取外掛來達成,當然,快取外掛也可以拿來快取其他東西。這讓建構過程中增加了額外的彈性層面。蠻酷的,哼?

我們也可以像 Grunt 一樣自動載入所有已安裝的外掛,但這不在此文章目的,所以我們將維持在手動的方式。

建立任務

編譯 Sass、Autoprefix 及縮小化

首先,我們設置編譯 Sass 。我們將編譯 Sass,接著通過 Autoprefixer,最後儲存結果到我們的目的地。最後產生一個縮小化的 .min 版本、自動重新整理頁面並且通知任務已經完成:

gulp.task('styles', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version'))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(rename({suffix: '.min'}))
    .pipe(cssnano())
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(notify({ message: 'Styles task complete' }));
});

再繼續下去之前,一個小小的說明。

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

這個 gulp.task API 用來建立任務。可以透過終端機輸入 $ gulp styles 指令來執行上述任務。

return sass('src/styles/main.scss', { style: 'expanded' })

這是新的 gulp-ruby-sass API,這裏我們定義了來源檔案,並且帶入任意選項。許多其他外掛,你會用 gulp.src API,稍後我們將會在此文章中用到(return gulp.src(...))。它允許使用 glob 樣式,例如 /**/*.scss 比對多個符合的檔案。傳回的串流(stream)讓它成為非同步機制,在我們收到完成通知之前,必須確保該任務已經全部完成。

.pipe(autoprefixer('last 2 version'))

我們使用 pipe() 來串流來源檔案到另個外掛。外掛的選項通常在它們各自的 Github 頁面中找到。上面列表中我有留下各個外掛的連結,讓你方便使用。串流是可串鏈的,所以你可以串流各式各樣的外掛在上面。

.pipe(gulp.dest('dist/assets/css'));

這個 gulp.dest() API 是用來設定目的路徑。一個任務可以有多個目的地,一個用來輸出擴展的版本,一個用來輸出縮小化的版本。這個在上述的 styles 任務中已經有展示。

建議閱讀 gulp 的 API 文件,以了解這些函式方法。它們並不像看起來的那樣可怕!

JSHint、拼接及縮小化 JavaScript

希望你現在對於如何建立一個新的 gulp 任務有好想法。接下來,我們將設定腳本任務,包括 lint、拼接及醜化:

gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(rename({suffix: '.min'}))
    .pipe(uglify())
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

這裡我們使用 gulp.src API 來指定輸入來源檔案。一件事提醒,我們需要指定JSHint一個 reporter。這裡我使用預設的 reporter,適用於大多數人。更多有關此設定,你可以從 JSHint 網站取得。

圖片壓縮

接著,我們將設定圖片壓縮:

gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
    .pipe(gulp.dest('dist/assets/img'))
    .pipe(notify({ message: 'Images task complete' }));
});

這會將對所有來源圖片進行 imagemin 處理。我們可以稍微更進一步,利用快取保存已經壓縮過的圖片,以便每次進行此任務時不需要再重新壓縮。這裡只需要gulp-cache外掛--稍早已經安裝。我們需要額外設置才能使用這個外掛,因此修改這段程式碼:

.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))

成為這段:

.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))

現在只有新的或更動過的圖片會被壓縮。乾淨俐落!

收拾乾淨!

在我們進行佈署之前,清除目的地目錄並重建檔案是一個好主意--以防萬一任何已經被刪除的來源檔案遺留在目的地目錄之中:

gulp.task('clean', function() {
    return del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img']);
});

在這裡我們不需要外掛,我們可以在 gulp 中直接使用 Node 模組。使用回呼函式(cb)來確保離開前任務已經完成。

預設任務

我們可以建立一個預設任務,當只輸入 $ gulp 指令時執行的任務,這裡執行三個我們所建立的任務:

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

注意額外傳入 gulp.task 的陣列。這裡我們可以定義任務相依(task dependencies)。在這個範例中,gulp.start 開始任務前會先執行清理任務。Gulp 中所有的任務都是並行(concurrently)處理,並沒有先後順序哪個任務會先完成,所以我們需要確保 clean 在其他任務開始之前執行完畢。

注意: 透過相依任務陣列來執行 clean 而非 gulp.start 是經過考慮的,在這個情境來看是最好的選擇,以確保清理任務全部完成。

看守

為了能夠看守檔案,並在更動發生後執行相關任務,首先需要建立一個新的任務,使用 gulp.watch API 來看守檔案:

gulp.task('watch', function() {

  // 看守 .scss 檔
  gulp.watch('src/styles/**/*.scss', ['styles']);

  // 看守 .js 檔
  gulp.watch('src/scripts/**/*.js', ['scripts']);

  // 看守圖片檔
  gulp.watch('src/images/**/*', ['images']);

});

透過 gulp.watch 指定想要看守的檔案,並且透過相依任務陣列定義任務。執行 $ gulp watch 來開始看守檔案,任何 .scss.js 或圖片檔案一旦有了更動,便會執行相對應的任務。

即時重整(LiveReload)

Gulp 也可以在處理檔案更動後,自動重新整理頁面。我們需要修改 watch 任務,設置即時重整伺服器。

gulp.task('watch', function() {

  // 建立即時重整伺服器
  livereload.listen();

  // 看守所有位在 dist/  目錄下的檔案,一旦有更動,便進行重整
  gulp.watch(['dist/**']).on('change', livereload.changed);

});

為了讓這個功能有效,除了伺服器之外,還需要安裝並啟用 LiveReload 的瀏覽器外掛。或者你也可以手動加上這個程式碼片段

全部放在一起

這裡是完整的 gulpfile,直接嵌入這個 gist:

我也將相同任務的 Gruntfile 放在同一個 gist,方便你做比較。

如果你有任何疑問或議題,請在文章下方留下評論或者可以在Twitter找到我。