Grunt靠邊,全新的建構工具來了。Gulp的code-over-configuration不只讓撰寫任務(tasks)更加容易,也更好閱讀及維護。
Glup使用node.js串流(streams)讓建構更快速,不須寫出資料到硬碟的暫存檔案/目錄。如果你想了解更多有關串流–雖然不是必須的–你可以閱讀這篇文章。Gulp利用來源檔案當作輸入,串流到一群外掛(plugins),最後取得輸出的結果,並非配置每一個外掛的輸入與輸出–就像Grunt。讓我們來看個範例,分別在Gulp及Grunt建構Sass:
Grunt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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:
1 2 3 4 5 6 |
gulp.task('sass', function() { return gulp.src('src/styles/main.scss') .pipe(sass({ style: 'compressed' })) .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:
1 |
$ npm install gulp -g |
這會將gulp安裝到全域環境下,讓你可以存取gulp的CLI。接著,需要在本地端的專案進行安裝。cd
到你的專案根目錄,執行下列指令(請先確定你有package.json
檔案):
1 |
$ npm install gulp --save-dev |
上述指令將gulp安裝到本地端的專案內,並紀錄於package.json
內的devDependencies
物件。
安裝gulp外掛
接著安裝一些外掛,完成下列任務:
- 編譯Sass (gulp-ruby-sass)
- Autoprefixer (gulp-autoprefixer)
- 縮小化(minify)CSS (gulp-minify-css)
- JSHint (gulp-jshint)
- 拼接 (gulp-concat)
- 醜化(Uglify) (gulp-uglify)
- 圖片壓縮 (gulp-imagemin)
- 即時重整(LiveReload) (gulp-livereload)
- 清理檔案 (gulp-clean)
- 圖片快取,只有更改過得圖片會進行壓縮 (gulp-cache)
- 更動通知 (gulp-notify)
執行下列指令來安裝這些外掛:
1 |
$ npm install gulp-ruby-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-clean gulp-notify gulp-rename gulp-livereload gulp-cache --save-dev |
指令將會安裝必要的外掛,並紀錄於package.json
內的devDependencies
物件。完整的gulp外掛清單可以在這裡找到。
載入外掛
接下來,我們需要建立一個gulpfile.js
檔案,並且載入這些外掛:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var gulp = require('gulp'), sass = require('gulp-ruby-sass'), autoprefixer = require('gulp-autoprefixer'), minifycss = require('gulp-minify-css'), jshint = require('gulp-jshint'), uglify = require('gulp-uglify'), imagemin = require('gulp-imagemin'), rename = require('gulp-rename'), clean = require('gulp-clean'), concat = require('gulp-concat'), notify = require('gulp-notify'), cache = require('gulp-cache'), livereload = require('gulp-livereload'); |
呼!看起來比Grunt有更多的事要做,對吧?Gulp外掛跟Grunt外掛有些許差異–它被設計成做一件事並且做好一件事。例如;Grunt的imagemin利用快取來避免重複壓縮已經壓縮好的圖片。在Gulp中,這必須透過一個快取外掛來達成,當然,快取外掛也可以拿來快取其他東西。這讓建構過程中增加了額外的彈性層面。蠻酷的,哼?
我們也可以像Grunt一樣自動載入所有已安裝的外掛,但這不在此文章目的,所以我們將維持在手動的方式。
建立任務
編譯Sass,Autoprefix及縮小化
首先,我們設定編譯Sass。我們將編譯Sass、接著通過Autoprefixer,最後儲存結果到我們的目的地。接著產生一個縮小化的.min
版本、自動重新整理頁面及通知任務已經完成:
1 2 3 4 5 6 7 8 9 10 |
gulp.task('styles', function() { return gulp.src('src/styles/main.scss') .pipe(sass({ 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')) .pipe(rename({suffix: '.min'})) .pipe(minifycss()) .pipe(gulp.dest('dist/assets/css')) .pipe(notify({ message: 'Styles task complete' })); }); |
繼續下去之前,一個小小的說明。
1 |
gulp.task('styles', function() { ... )}; |
這個gulp.task
API用來建立任務。可以透過終端機輸入$ gulp styles
指令來執行上述任務。
1 |
return gulp.src('src/styles/main.scss') |
這個gulp.src
API用來定義一個或多個來源檔案。允許使用glob樣式,例如/**/*.scss
比對多個符合的檔案。傳回的串流(stream)讓它成為非同步機制,所以在我們收到完成通知之前,確保該任務已經全部完成。
1 |
.pipe(sass({ style: 'expanded' })) |
使用pipe()
來串流來源檔案到某個外掛。外掛的選項通常在它們各自的Github頁面中可以找到。上面列表中我有留下各個外掛的連結,讓你方便使用。
1 |
.pipe(gulp.dest('dist/assets/css')); |
這個gulp.dest()
API是用來設定目的路徑。一個任務可以有多個目的地,一個用來輸出擴充套件的版本,一個用來輸出縮小化的版本。這個在上述的styles
任務中已經有展示。
我建議閱讀gulp的API檔案,以瞭解這些函式方法。它們並不像看起來的那樣可怕!
JSHint,拼接及縮小化JavaScript
希望你現在對於如何建立一個新的gulp任務有好想法。接下來,我們將設定指令碼任務,包括lint、拼接及醜化:
1 2 3 4 5 6 7 8 9 10 11 |
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' })); }); |
一件事提醒,我們需要指定JSHint一個reporter。這裡我使用預設的reporter,適用於大多數人。更多有關此設定,你可以在JSHint網站取得。
圖片壓縮
接著,我們將設定圖片壓縮:
1 2 3 4 5 6 |
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外掛–稍早已經安裝。我們需要額外設定才能使用這個外掛,因此修改這段程式碼:
1 |
.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })) |
成為這段:
1 |
.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true }))) |
現在只有新的或更動的圖片會被壓縮。乾淨俐落!
收拾乾淨
在我們進行佈署之前,清除目的地目錄並重建檔案是一個好主意–以防萬一任何已經被刪除的來源檔案遺留在目的地目錄之中:
1 2 3 4 |
gulp.task('clean', function() { return gulp.src(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], {read: false}) .pipe(clean()); }); |
我們可以傳入一個目錄(或檔案)陣列到gulp.src
。因為我們不想要讀取已經被刪除的檔案,我們可以加入read: false
選項來防止gulp讀取檔案內容–讓它快一些。
預設任務
我們可以建立一個預設任務,當只輸入$ gulp
指令時執行的任務,這裡執行三個我們所建立的任務:
1 2 3 |
gulp.task('default', ['clean'], function() { gulp.start('styles', 'scripts', 'images'); }); |
注意額外傳入gulp.task
的陣列。這裡我們可以定義任務相依(task dependencies)。在這個範例中,gulp.start
開始任務前會先執行清理(clean
)任務。Gulp中所有的任務都是並行(concurrently)執行,並沒有先後順序哪個任務會先完成,所以我們需要確保clean
任務在其他任務開始前完成。
注意: 透過相依任務陣列來執行clean
而非gulp.start
是經過考慮的,在這個情境來看是最好的選擇,以確保清理任務全部完成。
看守
為了能夠看守檔案,並在更動發生後執行相關任務,首先需要建立一個新的任務,使用gulp.watch
API來看守檔案:
1 2 3 4 5 6 7 8 9 10 11 12 |
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
任務,設定即時重整伺服器。
1 2 3 4 5 6 7 8 9 10 11 |
gulp.task('watch', function() { // 建立即時重整伺服器 var server = livereload(); // 看守所有位在 dist/ 目錄下的檔案,一旦有更動,便進行重整 gulp.watch(['dist/**']).on('change', function(file) { server.changed(file.path); }); }); |
為了讓這個功能有效,除了伺服器之外,還需要安裝並啟用LiveReload的瀏覽器外掛。或者你也可以手動加上這個片段程式碼。
全部在一起
這裡是完整的gulpfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
// 載入外掛 var gulp = require('gulp'), sass = require('gulp-ruby-sass'), autoprefixer = require('gulp-autoprefixer'), minifycss = require('gulp-minify-css'), jshint = require('gulp-jshint'), uglify = require('gulp-uglify'), imagemin = require('gulp-imagemin'), rename = require('gulp-rename'), clean = require('gulp-clean'), concat = require('gulp-concat'), notify = require('gulp-notify'), cache = require('gulp-cache'), livereload = require('gulp-livereload'); // 樣式 gulp.task('styles', function() { return gulp.src('src/styles/main.scss') .pipe(sass({ style: 'expanded', })) .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4')) .pipe(gulp.dest('dist/styles')) .pipe(rename({ suffix: '.min' })) .pipe(minifycss()) .pipe(gulp.dest('dist/styles')) .pipe(notify({ message: 'Styles task complete' })); }); // 指令碼 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/scripts')) .pipe(rename({ suffix: '.min' })) .pipe(uglify()) .pipe(gulp.dest('dist/scripts')) .pipe(notify({ message: 'Scripts task complete' })); }); // 圖片 gulp.task('images', function() { return gulp.src('src/images/**/*') .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))) .pipe(gulp.dest('dist/images')) .pipe(notify({ message: 'Images task complete' })); }); // 清理 gulp.task('clean', function() { return gulp.src(['dist/styles', 'dist/scripts', 'dist/images'], {read: false}) .pipe(clean()); }); // 預設任務 gulp.task('default', ['clean'], function() { gulp.start('styles', 'scripts', 'images'); }); // 看手 gulp.task('watch', function() { // 看守所有.scss檔 gulp.watch('src/styles/**/*.scss', ['styles']); // 看守所有.js檔 gulp.watch('src/scripts/**/*.js', ['scripts']); // 看守所有圖片檔 gulp.watch('src/images/**/*', ['images']); // 建立即時重整伺服器 var server = livereload(); // 看守所有位在 dist/ 目錄下的檔案,一旦有更動,便進行重整 gulp.watch(['dist/**']).on('change', function(file) { server.changed(file.path); }); }); |
你也可以在gist看整個gulpfile。我也將達到相同任務的Gruntfile放在同一個gist,方便做比較。
如果你有任何疑問或議題,請在文章下方留下評論或者可以在Twitter找到我。