gulp

編輯于2015年 轉載自某作者的譯文 作者要是看到請聯系我注明出處

對網站資源進行優化,并使用不同瀏覽器測試并不是網站設計過程中最有意思的部分,但是這個過程中的很多重復的任務能夠使用正確的工具自動完成,從而使效率大大提高,這是讓很多開發者覺得有趣的地方。
Gulp是一個構建系統,它能通過自動執行常見任務,比如編譯預處理CSS,壓縮JavaScript和刷新瀏覽器,來改進網站開發的過程。通過本文,我們將知道如何使用Gulp來改變開發流程,從而使開發更加快速高效。
What Is Gulp?
Gulp是一個構建系統,開發者可以使用它在網站開發過程中自動執行常見任務。Gulp是基于Node.js構建的,因此Gulp源文件和你用來定義任務的Gulp文件都被寫進了JavaScript(或者CoffeeScript)里。前端開發工程師還可以用自己熟悉的語言來編寫任務去lint JavaScript和CSS、解析模板以及在文件變動時編譯LESS文件(當然這些只是一小部分例子)。
Gulp本身雖然不能完成很多任務,但它有大量插件可用,開發者可以訪問插件頁面或者在npm搜索gulpplugin就能看到。例如,有些插件可以用來執行JSHint、編譯CoffeeScript執行Mocha測試,甚至更新版本號。
對比其他構建工具,比如Grunt,以及最近流行的Broccoli,我相信Gulp會更勝一籌(請看后面的”Why Gulp?”部分),同時我匯總了一個使用Javascript編寫的構建工具清單,可供大家參考。
Gulp是一個可以在GitHub上找到的開源項目。
Installing Gulp
安裝Gulp的過程十分簡單。首先,需要在全局安裝Gulp包:
npm install -g gulp

然后,在項目里面安裝Gulp:
npm install --save-dev gulp

Using Gulp
現在我們創建一個Gulp任務來壓縮JavaScript文件。首先創建一個名為gulpfile.js的文件,這是定義Gulp任務的地方,它可以通過gulp命令來運行,接著把下面的代碼放到gulpfile.js文件里面。
var gulp = require('gulp'), uglify = require('gulp-uglify');gulp.task('minify', function () { gulp.src('js/app.js') .pipe(uglify()) .pipe(gulp.dest('build'))});

然后在npm里面運行npm install -–save-dev gulp-uglify來安裝gulp-uglify,最后通過運行gulp minify來執行任務。假設js目錄下有個app.js文件,那么一個新的app.js將被創建在編譯目錄下,它包含了js/app.js的壓縮內容。想一想,到底發生了什么?
我們只在gulpfile.js里做了一點事情。首先,我們加載gulp和gulp-uglify模塊:
var gulp = require('gulp'), uglify = require('gulp-uglify');

然后,我們定義了一個叫minify的任務,它執行時會調用函數,這個函數會作為第二個參數:
gulp.task('minify', function () {});

最后,也是難點所在,我們需要定義任務應該做什么:
gulp.src('js/app.js') .pipe(uglify()) .pipe(gulp.dest('build'))

如果你對數據流非常熟悉(其實大多數前端開發人員并不熟悉),上面所提供的代碼對你來說就沒有太大意義了。
STREAMS
數據流能夠通過一系列的小函數來傳遞數據,這些函數會對數據進行修改,然后把修改后的數據傳遞給下一個函數。
在上面的例子中,gulp.src()函數用字符串匹配一個文件或者文件的編號(被稱為“glob”),然后創建一個對象流來代表這些文件,接著傳遞給uglify()函數,它接受文件對象之后返回有新壓縮源文件的文件對象,最后那些輸出的文件被輸入gulp.dest()函數,并保存下來。
整個數據流動過程如下圖所示: [圖片上傳中。。。(1)]
當只有一個任務的時候,函數并不會起太大的作用。然而,仔細思考下面的代碼:
gulp.task('js', function () { return gulp.src('js/*.js') .pipe(jshint()) .pipe(jshint.reporter('default')) .pipe(uglify()) .pipe(concat('app.js')) .pipe(gulp.dest('build'));});

在運行這段程序之前,你需要先安裝gulp,gulp-jshint,gulp-uglify和gulp-concat。
這個任務會讓所有的文件匹配js/.js(比如js目錄下的所有JavaScript文件),并且執行JSHint,然后打印輸出結果,取消文件縮進,最后把他們合并起來,保存為build/app.js,整個過程如下圖所示: [圖片上傳中。。。(2)]
如果你對Grunt 足夠熟悉,就會注意到,Gulp和Grunt的工作方式很不一樣。Grunt不使用數據流,而是使用文件,對文件執行單個任務然后保存到新的文件中,每個任務都會重復執行所有進程,文件系統頻繁的處理任務會導致Grunt的運行速度比Gulp慢。
如果想要獲取更加全面的數據流知識,請查看“Stream Handbook”.
GULP.SRC()
gulp.src()方法輸入一個glob(比如匹配一個或多個文件的字符串)或者glob數組,然后返回一個可以傳遞給插件的數據流。
Gulp使用node-glob來從你指定的glob里面獲取文件,這里列舉下面的例子來闡述,方便大家理解:
js/app.js 精確匹配文件
js/
.js 僅匹配js目錄下的所有后綴為.js的文件
js/*/.js 匹配js目錄及其子目錄下所有后綴為.js的文件
!js/app.js 從匹配結果中排除js/app.js,這種方法在你想要匹配除了特殊文件之外的所有文件時非常管用
*.+(js|css) 匹配根目錄下所有后綴為.js或者.css的文件

此外,Gulp也有很多其他的特征,但并不常用。如果你想了解更多的特征,請查看Minimatch文檔。
js目錄下包含了壓縮和未壓縮的JavaScript文件,現在我們想要創建一個任務來壓縮還沒有被壓縮的文件,我們需要先匹配目錄下所有的JavaScript文件,然后排除后綴為.min.js的文件:
gulp.src(['js//.js', '!js//.min.js'])

DEFINING TASKS
gulp.task()函數通常會被用來定義任務。當你定義一個簡單的任務時,需要傳入任務名字和執行函數兩個屬性。
gulp.task('greet', function () { console.log('Hello world!');});

執行gulp greet的結果就是在控制臺上打印出“Hello world”.
一個任務有時也可以是一系列任務。假設要定義一個任務build來執行css、js、imgs這三個任務,我們可以通過指定一個任務數組而不是函數來完成。
gulp.task('build', ['css', 'js', 'imgs']);

這些任務不是同時進行的,所以你不能認為在js任務開始的時候css任務已經結束了,也可能還沒有結束。為了確保一個任務在另一個任務執行前已經結束,可以將函數和任務數組結合起來指定其依賴關系。例如,定義一個css任務,在執行前需要檢查greet任務是否已經執行完畢,這樣做就是可行的:
gulp.task('css', ['greet'], function () { // Deal with CSS here});

現在,當執行css任務時,Gulp會先執行greet任務,然后在它結束后再調用你定義的函數。
DEFAULT TASKS
你可以定義一個在gulp開始運行時候默認執行的任務,并將這個任務命名為“default”:
gulp.task('default', function () { // Your default task});

PLUGINS
Gulp上有超過600種插件供你選擇,你可以在插件頁面或者npm上搜索gulpplugin來瀏覽插件列表。有些擁有“gulpfriendly”標簽的插件,他們不能算插件,但是能在Gulp上正常運行。 需要注意的是,當直接在npm里搜索時,你無法知道某一插件是否在黑名單上(你需要滾動到插件頁面底部才能看到)。
大多數插件的使用都很方便,它們都配有詳細的文檔,而且調用方法也相同(通過傳遞文件對象流給它),它們通常會對這些文件進行修改(但是有一些插件例外,比如validators),最后返回新的文件給下一個插件。
讓我們用前面的js任務來詳細說明一下:
var gulp = require('gulp'), jshint = require('gulp-jshint'), uglify = require('gulp-uglify'), concat = require('gulp-concat');gulp.task('js', function () { return gulp.src('js/*.js') .pipe(jshint()) .pipe(jshint.reporter('default')) .pipe(uglify()) .pipe(concat('app.js')) .pipe(gulp.dest('build'));});

這里使用了三個插件,gulp-jshint,gulp-uglifygulp-concat。開發者可以參考插件的README文檔,插件有很多配置選項,而且給定的初始值通常能滿足需求。細心的讀者可能會發現,程序中JSHint插件執行了2次,這是因為第一次執行JSHint只是給文件對象附加了jshint屬性,并沒有輸出。你可以自己讀取jshint的屬性或者傳遞給默認的JSHint的接收函數或者其他的接收函數,比如jshint-stylish.
其他兩個插件的作用很清楚:uglify()函數壓縮代碼,concat(‘app.js’)函數將所有文件合并到一個叫app.js的文件中。
GULP-LOAD-PLUGINS
我發現gulp-load-plugin模塊十分有用,它能夠自動地從package.json中加載任意Gulp插件然后把它們附加到一個對象上。它的基本用法如下所示:
var gulpLoadPlugins = require('gulp-load-plugins'), plugins = gulpLoadPlugins();

你可以把所有代碼寫到一行,但是我并不推薦這樣做。
在執行那些代碼之后,插件對象就已經包含了插件,并使用“駝峰式”的方式進行命名(例如,gulp-ruby-sass將被加載成plugins.rubySass),這樣就可以很方便地使用了。例如,前面的js任務簡化為如下:
var gulp = require('gulp'), gulpLoadPlugins = require('gulp-load-plugins'), plugins = gulpLoadPlugins();gulp.task('js', function () { return gulp.src('js/*.js') .pipe(plugins.jshint()) .pipe(plugins.jshint.reporter('default')) .pipe(plugins.uglify()) .pipe(plugins.concat('app.js')) .pipe(gulp.dest('build'));});

假設package.json文件如下面所示:
{ "devDependencies": { "gulp-concat": "~2.2.0", "gulp-uglify": "~0.2.1", "gulp-jshint": "~1.5.1", "gulp": "~3.5.6" }}

這個例子雖然已經夠短了,但是使用更長更復雜的Gulp文件會把它們簡化成一兩行代碼。
三月初發布的Gulp-load-plugins0.4.0版本添加了延遲加載功能,提高了插件的性能,因為插件在使用的時候才會被加載進來,你不用擔心package.json里未被使用的插件影響性能(但是你需要把他們清理掉)。換句話說,如果你在執行任務時只需要兩個插件,那么其他不相關的插件就不會被加載。
WATCHING FILES
Gulp可以監聽文件的修改動態,然后在文件被改動的時候執行一個或多個任務。這個特性十分有用(對我來說,這可能是Gulp中最有用的一個功能)。你可以保存LESS文件,接著Gulp會自動把它轉換為CSS文件并更新瀏覽器。
使用gulp.watch()方法可以監聽文件,它接受一個glob或者glob數組(和gulp.src()一樣)以及一個任務數組來執行回調。
讓我們看看下面,build任務可以將模板轉換成html格式,然后我們希望定義一個watch任務來監聽模板文件的變化,并將這些模板轉換成html格式。watch函數的使用方法如下所示:
gulp.task('watch', function () { gulp.watch('templates/*.tmpl.html', ['build']);});

現在,當改變一個模板文件時,build任務會被執行并生成HTML文件,也可以給watch函數一個回調函數,而不是一個任務數組。在這個示例中,回調函數有一個包含觸發回調函數信息的event對象:
gulp.watch('templates/*.tmpl.html', function (event) { console.log('Event type: ' + event.type); // added, changed, or deleted console.log('Event path: ' + event.path); // The path of the modified file});

Gulp.watch()的另一個非常好的特性是返回我們熟知的watcher。利用watcher來監聽額外的事件或者向watch中添加文件。例如,在執行一系列任務和調用一個函數時,你就可以在返回的watcher中添加監聽change事件:
var watcher = gulp.watch('templates/*.tmpl.html', ['build']);watcher.on('change', function (event) { console.log('Event type: ' + event.type); // added, changed, or deleted console.log('Event path: ' + event.path); // The path of the modified file});

除了change事件,還可以監聽很多其他的事件:
end 在watcher結束時觸發(這意味著,在文件改變的時候,任務或者回調不會執行)
error 在出現error時觸發
ready 在文件被找到并正被監聽時觸發
nomatch 在glob沒有匹配到任何文件時觸發

Watcher對象也包含了一些可以調用的方法:
watcher.end() 停止watcher(以便停止執行后面的任務或者回調函數)
watcher.files() 返回watcher監聽的文件列表
watcher.add(glob) 將與指定glob相匹配的文件添加到watcher(也接受可選的回調當第二個參數)
watcher.remove(filepath) 從watcher中移除個別文件

Reloading Changes In The Browser
當一個文件被修改或者Gulp任務被執行時可以用Gulp來加載或者更新網頁。LiveReload和BrowserSync插件就可以用來實現在游覽器中加載更新的內容。
LIVERELOAD
LiveReload結合了瀏覽器擴展(包括Chrome extension),在發現文件被修改時會實時更新網頁。它可以和gulp-watch插件或者前面描述的gulp-watch()函數一起使用。下面有一個gulp-livereload倉庫中的README文件提到的例子:
var gulp = require('gulp'), less = require('gulp-less'), livereload = require('gulp-livereload'), watch = require('gulp-watch');gulp.task('less', function() { gulp.src('less/*.less') .pipe(watch()) .pipe(less()) .pipe(gulp.dest('css')) .pipe(livereload());});

這會監聽到所有與less/*.less相匹配的文件的變化。一旦監測到變化,就會生成css并保存,然后重新加載網頁.
BROWSERSYNC
BroserSync在瀏覽器中展示變化的功能與LiveReload非常相似,但是它有更多的功能。
當你改變代碼的時候,BrowserSync會重新加載頁面,或者如果是css文件,會直接添加進css中,頁面并不需要再次刷新。這項功能在網站是禁止刷新的時候是很有用的。假設你正在開發單頁應用的第4頁,刷新頁面就會導致你回到開始頁。使用LiveReload的話,你就需要在每次改變代碼之后還需要點擊四次,而當你修改CSS時,插入一些變化時,BrowserSync會直接將需要修改的地方添加進CSS,就不用再點擊回退。


BrowserSync提供了一種在多個瀏覽器里測試網頁的很好方式(查看大圖)。
BrowserSync也可以在不同瀏覽器之間同步點擊翻頁、表單操作、滾動位置。你可以在電腦和iPhone上打開不同的瀏覽器然后進行操作。所有設備上的鏈接將會隨之變化,當你向下滾動頁面時,所有設備上頁面都會向下滾動(通常還很流暢?。?。當你在表單中輸入文本時,每個窗口都會有輸入。當你不想要這種行為時,也可以把這個功能關閉。

BrowserSync不需要使用瀏覽器插件,因為它本身就可以給你提供文件。(查看大圖)
BrowserSync不需要使用瀏覽器插件,因為它本身就可以為你提供文件服務(如果文件是動態的,則為他們提供代理服務)和用來開啟瀏覽器和服務器之間的socket的腳本服務。到目前為止這個功能的使用都十分順暢。
實際上BrowserSync對于Gulp并不算一種插件,因為BrowserSync并不像一個插件一樣操作文件。然而,npm上的BrowserSync模塊能在Gulp上被直接調用。
首先,需要通過npm安裝一下:
npm install --save-dev browser-sync

然后gulpfile.js會啟動BrowserSync并監聽文件:
var gulp = require('gulp'), browserSync = require('browser-sync');gulp.task('browser-sync', function () { var files = [ 'app//.html', 'app/assets/css//.css', 'app/assets/imgs//.png', 'app/assets/js//.js' ]; browserSync.init(files, { server: { baseDir: './app' } });});

執行gulp browser-sync后會監聽匹配文件的變化,同時為app目錄提供文件服務。
此外BrowserSync的開發者還寫了很多關于BrowserSync+Gulp倉庫的其他用途。
Why Gulp?
前面提到過,Gulp是為數不多的使用JavaScript開發的構建工具之一,也有其他不是用JavaScript開發的構建工具,比如Rake,那么我們為什么要選擇Gulp呢?
目前最流行的兩種使用JavaScript開發的構建工具是Grunt和Gulp。Grunt在2013年非常流行,因為它徹底改變了許多人開發網站的方式,它有上千種插件可供用戶使用,從linting、壓縮、合并代碼到使用Bower安裝程序包,啟動Express服務都能辦到。這些和Gulp的很不一樣,Gulp只有執行單個小任務來處理文件的插件,因為任務都是JavaScript(和Grunt使用的大型對象不同),根本不需要插件,你只需用傳統方法啟動一個Express服務就可以了。
Grunt任務擁有大量的配置,會引用大量你實際上并不需要的對象屬性,但是Gulp里同樣的任務也許只有幾行。讓我們看個簡單的Gruntfile.js,它規定一個將LESS轉換為CSS的任務,然后執行Autoprefixer:
grunt.initConfig({ less: { development: { files: { "build/tmp/app.css": "assets/app.less" } } }, autoprefixer: { options: { browsers: ['last 2 version', 'ie 8', 'ie 9'] }, multiple_files: { expand: true, flatten: true, src: 'build/tmp/app.css', dest: 'build/' } }});grunt.loadNpmTasks('grunt-contrib-less');grunt.loadNpmTasks('grunt-autoprefixer');grunt.registerTask('css', ['less', 'autoprefixer']);

與Gulpfile.js文件進行對比,它們執行的任務相同:
var gulp = require('gulp'), less = require('gulp-less'), autoprefix = require('gulp-autoprefixer');gulp.task('css', function () { gulp.src('assets/app.less') .pipe(less()) .pipe(autoprefix('last 2 version', 'ie 8', 'ie 9')) .pipe(gulp.dest('build'));});

因為Grunt比Gulp更加頻繁地操作文件系統,所以使用數據流的Gulp總是比Grunt快。對于一個小的LESS文件,gulpfile.js通常需要6ms,而gruntfile.js則需要大概50ms——慢8倍多。這只是個簡單的例子,對于長的文件,這個數字會增加得更顯著。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數,API也非常簡單,學...
    依依玖玥閱讀 3,171評論 7 55
  • 對網站資源進行優化,并使用不同瀏覽器測試并不是網站設計過程中最有意思的部分,但是這個過程中的很多重復的任務能夠使用...
    懵逼js閱讀 1,081評論 0 8
  • 1、gulp的安裝 首先確保你已經正確安裝了nodejs環境。然后以全局方式安裝gulp: npm install...
    F_imok閱讀 2,395評論 1 11
  • 在現在的前端開發中,前后端分離、模塊化開發、版本控制、文件合并與壓縮、mock數據等等一些原本后端的思想開始...
    Charlot閱讀 5,464評論 1 32
  • gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數,API也非常簡單,學...
    井皮皮閱讀 1,319評論 0 10