也算是用了半年Grunt,幾個月前也寫過一篇它的入門文章(點此查看),不得不說它是前端項目的一個得力助手。不過技術工具跟語言一樣日新月異,總會有更好用的新的東西把舊的拍死在沙灘上(當然Grunt肯定沒死,gulp也不是多新穎的東西)。
看標題很明顯知道相比Grunt,我會更為推崇gulp,不是說Grunt不好,而是gulp效率更高、健壯性更好,配置也更為簡單,自然也值得我們為它任性一回。
先談談上述提到的gulp的優越性:
一. 性能更高
相對Grunt頻繁的IO操作讀寫,gulp是將項目任務流程以streaming(流)的形式來做管道化處理,這句話怎么理解呢?
比如一個文件A,Grunt按順序會有三個組件任務a、b、c 要對起進行處理,那么流程可能是這樣的:
讀取A → A.a() → 寫A → 讀取A → A.b() → 寫A → 讀取A → A.c() → 寫A → complete/watch...
那么換成gulp又是怎樣的一個過程,它大概是這么處理:
讀取A並轉為流信息 → A.a() → A.b() → A.c() → 寫A → complete/watch...
可以看到執行任務的過程,是減少了(3-1)處對文件A的讀/寫操作,減輕了磁盤IO操作負擔,效率自然也得到明顯提升。
至於“流”的概念,可以戳這里了解一下。大致是讀取文件時,不按常規那樣把文件一口氣寫入內存中,而是把文件轉為數據流,收到多少數據就處理多少數據,像經過竹筒的涓涓流水那樣,有水源過來了就馬上處理后(你可以假設竹筒在這里有凈水的過濾作用)再送出去,直到水源全部“流”過該竹筒。
那么“管道化”處理又是怎樣的概念?
它很類似jQuery中的鏈式寫法,在nodeJS中管道式方法的api一般為.pipe() ,比如
XXXX.pipe(a()) //處理a任務 .pipe(b()) //處理b任務 .pipe(c()) //處理c任務
你也可以理解為上述的涓涓流水經過一排竹筒后,緊接着流向下一個竹筒,一直這樣循環,直到流過最后一個竹筒(當然流水依舊代表數據,竹筒代表任務)。
二. 健壯性更好
gulp走的是遞歸編譯的解析方式,有助於項目優化的健壯性。打個比方,比如我們使用sass來編寫樣式,其中b.scss引入了_a.scss文件,如果我們修改了_a.scss的內容,gulp會即時更新b.scss對應的b.css編譯文件,但Grunt是基於緩存機制的,故不會重新編譯b.scss文件,導致問題。
三. 配置更簡潔
其實之前寫的那篇Grunt入門的文章,我的確不懂到底要如何介紹Gruntfile.js的配置——略復雜和混亂,鄙人口才也不好,便草草幾句話帶過,相信讀者可能也不太能夠理清頭緒。而gulp使用了node的流式管道化(pipe)處理,其配置和寫法變得簡潔、統一了許多,自然也方便理解。
為你的項目搭建gulp任務
搭建gulp其實很簡單,就倆步驟——在項目根目錄下安裝所需組件,並配置一份gulpfile.js。
我們假設我們有一個放在D盤下的項目 D:/project,里面的文件結構是這樣的:
我們打算利用gulp把RAW文件夾下的js和sass文件編譯/壓縮后都輸出到COMPRESS文件夾下。
一. 安裝組件
gulp自己有官方的組件推薦/查找頁面(點我進入),就我們上述項目要求而言,我個人推薦下述幾個組件:
gulp //這個是必須安裝的,沒有它,其它組件都用不了(注意watch組件直接集成在gulp中了,無需額外安裝watch組件)
gulp-sass //編譯sass用的
gulp-sourcemaps //編譯sass時生成額外的.map文件用的(有啥用?看我這篇文章)
gulp-mini-css //壓縮css使用的
gulp-uglify //壓縮、混淆js文件用的
組件的安裝很簡單,直接npm安裝即可,比如安裝gulp:
npm install gulp
安裝過程可能會遇到組件被牆導致連接不上鏡像的問題,解決方法有兩個,一個是翻牆,另一個是走cnpm鏡像(點此查閱)。
組件全部安裝完成后會在你的項目根目錄下生成一個node_modules文件夾,用來存放組件模塊:
二. gulpfile.js文件配置
如同Grunt需要配置Gruntfile.js文件來告知node我要用什么組件並以怎樣的流程來執行任務,gulp也需要在項目根目錄配置一個gulpfile.js文件。
我們新建一個gulpfile.js文件,先在之中輸入下面內容:
var gulp = require('gulp'), sass = require('gulp-sass'), mincss = require('gulp-mini-css'), sourcemaps = require('gulp-sourcemaps'), uglify = require('gulp-uglify');
濃濃的commonJS風,告知node我們要使用哪些組件模塊來完成任務。
接着我們進一步配置文件,告訴node我們具體要以怎樣的流程來執行任務,這是完整的gulpfile.js文件內容:
var gulp = require('gulp'), sass = require('gulp-sass'), mincss = require('gulp-mini-css'), sourcemaps = require('gulp-sourcemaps'), uglify = require('gulp-uglify'); var raw_css = './RAW/css', com_css = './COMPRESS/css', raw_js = './RAW/js', com_js = './COMPRESS/js'; gulp.task('sass', function () { gulp.src(raw_css+'/**/*.scss') .pipe(sourcemaps.init()) .pipe(sass())
.pipe(mincss())
.pipe(sourcemaps.write('/')) .pipe(gulp.dest(com_css)); }); gulp.task('mincss', function () { gulp.src(com_css+'/**/*.scss') .pipe(mincss()) .pipe(gulp.dest(com_css)); }); gulp.task('minjs', function () { gulp.src(raw_js+'/**/*.js') .pipe(uglify()) .pipe(gulp.dest(com_js)); }); gulp.task('watch', function () { gulp.watch(raw_css+'/**/*.scss',['sass']); gulp.watch(raw_js+'/**/*.js',['minjs']); }); gulp.task('default',function(){ gulp.run('sass','minjs','mincss'); gulp.run('watch'); });
我們拿寫的最長的sass編譯模塊配置代碼塊來注釋說明下:
//每個gulp.task(name, fn)都是一個任務配置模塊,如本代碼段定義了名為"sass"的任務的執行流程 gulp.task('sass', function () { gulp.src(raw_css+'/**/*.scss') //gulp.src(glob)返回了一個可讀的stream,如此行返回了RAW/css/下的全部(包含子文件夾里的).scss文件流 .pipe(sourcemaps.init()) //.pipe()管道化執行組件任務,此處調用gulp-sourcemaps的初始化api來處理接收的文件流(方便后續編譯出.map文件) .pipe(sass()) //執行gulp-sass組件任務,把.scss文件流編譯為.css文件流 .pipe(sourcemaps.write('/')) //調用gulp-sourcemaps的寫入api,額外輸出.map文件流 .pipe(mincss()) //執行gulp-mini-css組件任務,壓縮所有css文件流 .pipe(gulp.dest(com_css)); //gulp.dest(glob)返回一個可寫的stream,如此行是將文件流寫入到 COMPRESS/css 里的對應路徑下 });
接着看看末尾處的兩個代碼段:
gulp.task('watch', function () { //定義名為"watch"的任務 gulp.watch(raw_css+'/**/*.scss',['sass']); //監聽 RAW/css 下的全部.scss文件,若有改動則執行名為'sass'任務 gulp.watch(raw_js+'/**/*.js',['minjs']); //監聽 RAW/js 下的全部.js文件,若有改動則執行名為'minjs'任務 }); gulp.task('default',function(){ //每個gulpfile.js里都應當有一個dafault任務,它是缺省任務入口(類似C語言的main()入口),運行gulp的時候實際只是調用該任務(從而來調用其它的任務) gulp.run('sass','minjs','mincss'); //gulp.run(tasks)表示運行對應的任務,這里表示執行名為'sass','minjs','mincss'的三個任務 gulp.run('watch'); //執行'watch'監聽任務 });
上述的兩個步驟做好后,直接輸入運行 gulp 指令即可(我是在webstorm里直接寫命令的):
同時可以看到COMPRESS文件夾下已經有了我們想要的編譯、壓縮后的文件:
另外這時候gulp已轉入監聽模式(雖然不像Grunt那樣有“watch...”的提示),只要你修改了被監聽的文件,任務會立即被執行一遍,無需再手動gulp一次。
用下來還是覺得gulp是個好東西,比Grunt更易上手,效率也更好,不過相較Grunt的“grunt-newer”組件,gulp對應功能的兩個組件“gulp-changed”和"gulp-newer"貌似運行起來都達不到預想效果,比如我希望只針對我修改了的某個sass文件來做任務處理,我做了如下配置:

var gulp = require('gulp'), sass = require('gulp-sass'), mincss = require('gulp-mini-css'), sourcemaps = require('gulp-sourcemaps'), changed = require('gulp-changed'), uglify = require('gulp-uglify'); var raw_css = './RAW/css', com_css = './COMPRESS/css', raw_js = './RAW/js', com_js = './COMPRESS/js'; gulp.task('sass', function () { gulp.src(raw_css+'/*.scss') .pipe(changed(raw_css+'/*.scss')) .pipe(sourcemaps.init()) .pipe(sass()) .pipe(sourcemaps.write('/')) .pipe(mincss()) .pipe(gulp.dest(com_css)); }); gulp.task('mincss', function () { gulp.src(com_css+'/*.scss') .pipe(changed(com_css)) .pipe(mincss()) .pipe(gulp.dest(com_css)); }); gulp.task('minjs', function () { gulp.src(raw_js+'/*.js') .pipe(changed(com_js)) .pipe(uglify()) .pipe(gulp.dest(com_js)); }); gulp.task('watch', function () { gulp.watch(raw_css+'/*.scss',['sass']); gulp.watch(raw_js+'/*.js',['minjs']); }); gulp.task('default',function(){ gulp.run('sass','minjs','mincss'); gulp.run('watch'); });
結果發現依舊是整個文件夾下的文件都被做了任務處理。或許是我的配置不正確,若有了解newer或者changed的朋友望能不吝指正。
另外百度fis貌似也是另一個不錯的alternative,跟gulp差不多,而且中文文檔也挺詳細的,之前用過百度的一些東西,感覺質量還是有保障的。
最后推薦下一個大神級朋友的博客,在我們苦口婆心的催產下他終於發表了第一篇文章,介紹的JS面向對象的思想,歡迎大家去看一看。
共勉~