===================css
對網站資源進行優化,並使用不一樣瀏覽器測試並非網站設計過程當中最有意思的部分,可是這個過程當中的不少重複的任務可以使用正確的工具自動完成,從而使效率大大提升,這是讓不少開發者以爲有趣的地方。html
Gulp是一個構建系統,它能經過自動執行常見任務,好比編譯預處理CSS,壓縮JavaScript和刷新瀏覽器,來改進網站開發的過程。經過本文,咱們將知道如何使用Gulp來改變開發流程,從而使開發更加快速高效。前端
Gulp是一個構建系統,開發者可使用它在網站開發過程當中自動執行常見任務。Gulp是基於Node.js構建的,所以Gulp源文件和你用來定義任務的Gulp文件都被寫進了JavaScript(或者CoffeeScript)裏。前端開發工程師還能夠用本身熟悉的語言來編寫任務去lint JavaScript和CSS、解析模板以及在文件變更時編譯LESS文件(固然這些只是一小部分例子)。node
Gulp自己雖然不能完成不少任務,但它有大量插件可用,開發者能夠訪問插件頁面或者在npm搜索gulpplugin就能看到。例如,有些插件能夠用來執行JSHint、編譯CoffeeScript,執行Mocha測試,甚至更新版本號。git
對比其餘構建工具,好比Grunt,以及最近流行的Broccoli,我相信Gulp會更勝一籌(請看後面的」Why Gulp?」部分),同時我彙總了一個使用Javascript編寫的構建工具清單,可供你們參考。github
Gulp是一個能夠在GitHub上找到的開源項目。web
安裝Gulp的過程十分簡單。首先,須要在全局安裝Gulp包:chrome
npm install -g gulp
而後,在項目裏面安裝Gulp:npm
npm install --save-dev gulp
如今咱們建立一個Gulp任務來壓縮JavaScript文件。首先建立一個名爲gulpfile.js的文件,這是定義Gulp任務的地方,它能夠經過gulp命令來運行,接着把下面的代碼放到gulpfile.js文件裏面。json
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'))
若是你對數據流很是熟悉(其實大多數前端開發人員並不熟悉),上面所提供的代碼對你來講就沒有太大意義了。
數據流可以經過一系列的小函數來傳遞數據,這些函數會對數據進行修改,而後把修改後的數據傳遞給下一個函數。
在上面的例子中,gulp.src()函數用字符串匹配一個文件或者文件的編號(被稱爲「glob」),而後建立一個對象流來表明這些文件,接着傳遞給uglify()函數,它接受文件對象以後返回有新壓縮源文件的文件對象,最後那些輸出的文件被輸入gulp.dest()函數,並保存下來。
整個數據流動過程以下圖所示:
當只有一個任務的時候,函數並不會起太大的做用。然而,仔細思考下面的代碼:
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,整個過程以下圖所示:
若是你對Grunt 足夠熟悉,就會注意到,Gulp和Grunt的工做方式很不同。Grunt不使用數據流,而是使用文件,對文件執行單個任務而後保存到新的文件中,每一個任務都會重複執行全部進程,文件系統頻繁的處理任務會致使Grunt的運行速度比Gulp慢。
若是想要獲取更加全面的數據流知識,請查看「Stream Handbook」.
gulp.src()方法輸入一個glob(好比匹配一個或多個文件的字符串)或者glob數組,而後返回一個能夠傳遞給插件的數據流。
Gulp使用node-glob來從你指定的glob裏面獲取文件,這裏列舉下面的例子來闡述,方便你們理解:
此外,Gulp也有不少其餘的特徵,但並不經常使用。若是你想了解更多的特徵,請查看Minimatch文檔。
js目錄下包含了壓縮和未壓縮的JavaScript文件,如今咱們想要建立一個任務來壓縮尚未被壓縮的文件,咱們須要先匹配目錄下全部的JavaScript文件,而後排除後綴爲.min.js的文件:
gulp.src(['js/**/*.js', '!js/**/*.min.js'])
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任務,而後在它結束後再調用你定義的函數。
你能夠定義一個在gulp開始運行時候默認執行的任務,並將這個任務命名爲「default」:
gulp.task('default', function () { // Your default task });
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-uglify和gulp-concat。開發者能夠參考插件的README文檔,插件有不少配置選項,並且給定的初始值一般能知足需求。細心的讀者可能會發現,程序中JSHint插件執行了2次,這是由於第一次執行JSHint只是給文件對象附加了jshint屬性,並無輸出。你能夠本身讀取jshint的屬性或者傳遞給默認的JSHint的接收函數或者其餘的接收函數,好比jshint-stylish.
其餘兩個插件的做用很清楚:uglify()函數壓縮代碼,concat(‘app.js’)函數將全部文件合併到一個叫app.js的文件中。
我發現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裏未被使用的插件影響性能(可是你須要把他們清理掉)。換句話說,若是你在執行任務時只須要兩個插件,那麼其餘不相關的插件就不會被加載。
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事件,還能夠監聽不少其餘的事件:
Watcher對象也包含了一些能夠調用的方法:
當一個文件被修改或者Gulp任務被執行時能夠用Gulp來加載或者更新網頁。LiveReload和BrowserSync插件就能夠用來實如今遊覽器中加載更新的內容。
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並保存,而後從新加載網頁.
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倉庫的其餘用途。
前面提到過,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倍多。這只是個簡單的例子,對於長的文件,這個數字會增長得更顯著。