參照Gulp for Beginners來學(xué)習(xí)Gulp基本內(nèi)容。以下為學(xué)習(xí)記錄筆記。
安裝Gulp
首先需要安裝Node.js,并在控制臺(tái)輸入$ npm install gulp -g
Mac端需要寫成$ sudo npm install gulp -g
(因?yàn)镸ac端需要在管理員下)運(yùn)行來安裝Gulp,-g
表示全局安裝。
創(chuàng)建一個(gè)Gulp項(xiàng)目
學(xué)習(xí)過程,將使用一個(gè)名為project的文件夾作為項(xiàng)目根目錄,在文件夾內(nèi)運(yùn)行$ npm init
,這將會(huì)為你的項(xiàng)目創(chuàng)建一個(gè)叫package.json的文件,文件會(huì)存儲(chǔ)關(guān)于你的項(xiàng)目的信息,就像在項(xiàng)目里使用的依賴(Gulp就是依賴的一個(gè)例子)
在創(chuàng)建了package.json之后,就可以通過$ npm install gulp --save-dev
在項(xiàng)目中安裝Gulp,這次我們是把Gulp安裝到project項(xiàng)目中,而不是全局安裝。(這次Mac端也不需要使用sudo)--save-dev
意思是把gulp作為依賴添加到當(dāng)前項(xiàng)目。
現(xiàn)在,可以在原來的文件夾中看到一個(gè)node_modules
文件夾,在node_modules
文件夾中包含著gulp
文件夾。
已經(jīng)做好使用Gulp的準(zhǔn)備工作,在我們使用Gulp之前我們必須清楚我們將要怎么在項(xiàng)目中使用Gulp,其中之一就是決定目錄結(jié)構(gòu)。
確定文件結(jié)構(gòu)
在這個(gè)結(jié)構(gòu)中,
app
文件夾是用來做開發(fā)的,dist
文件夾是用來包含生產(chǎn)現(xiàn)場(chǎng)的優(yōu)化文件。因?yàn)?code>app是用于開發(fā),所以我們所有的代碼都會(huì)放在
app
文件夾下。在配置Gulp時(shí),我們必須記住文件夾的結(jié)構(gòu)。現(xiàn)在就可以開始在存儲(chǔ)所有Gulp配置的gulpfile.js創(chuàng)建第一個(gè)Gulp任務(wù)。
寫你的第一個(gè)Gulp任務(wù)
使用Gulp的第一步是把它require
到gulpfile中
var gulp=require('gulp');
這個(gè)require
語(yǔ)句會(huì)告訴Node在node_modules
文件夾中查找一個(gè)叫gulp
的包,一旦找到,就會(huì)把包的內(nèi)容賦值到變量gulp
中。
接下來就可以使用這個(gè)gulp
變量來寫gulp
任務(wù),一個(gè)gulp
任務(wù)的基礎(chǔ)語(yǔ)法是:
gulp.task('task-name',function(){
//stuff here
});
task-name
指的是任務(wù)的名字,將會(huì)被用在你想在Gulp
運(yùn)行一個(gè)任務(wù)的時(shí)候。你也可以使用命令行gulp task-name
來運(yùn)行相同的任務(wù)。
測(cè)試?yán)樱?/p>
var gulp=require('gulp');
gulp.task('hello',function(){
console.log('Hello world!');
});
我們可以通過在控制臺(tái)輸入$ gulp hello
來運(yùn)行這個(gè)任務(wù)。
就可以在控制臺(tái)看到以下的結(jié)果:
Gulp任務(wù)通常會(huì)比這個(gè)復(fù)雜,通常會(huì)包含兩個(gè)額外的Gulp方法,和各種Gulp插件。
以下是一個(gè)真實(shí)任務(wù)可能的情況:
gulp.task('task-name', function () {
return gulp.src('source-files') // Get source files withgulp.src
.pipe(aGulpPlugin()) // Sends it through a gulp plugin
.pipe(gulp.dest('destination')) // Outputs the file in the destination folder
})
可以看到,一個(gè)真實(shí)的任務(wù)會(huì)用到兩個(gè)額外的gulp方法——gulp.src
和gulp.dest
gulp.src
告訴Gulp 任務(wù)在這個(gè)任務(wù)中使用哪些文件。gulp.dest
告訴Gulp當(dāng)任務(wù)完成時(shí)應(yīng)該在哪里輸出。
用Gulp預(yù)處理
使用一個(gè)叫gulp-sass
的插件可以在Gulp將Sass編譯成CSS,使用像安裝Gulp一樣的npm install
命令安裝gulp-sass
到項(xiàng)目中:
$ npm install gulp-sass --save-dev
使用--save-dev
標(biāo)記來保證gulp-sass被添加到package.json
中的devDependencies
中。
PS:在下載插件的過程中可能會(huì)遇到下載較慢的問題,可通過調(diào)用國(guó)內(nèi)鏡像來解決 來源
鏡像使用方法(三種辦法任意一種都能解決問題,建議使用第三種,將配置寫死,下次用的時(shí)候配置還在):
1.通過config命令
npm config set registry https://registry.npm.taobao.org npm info underscore (如果上面配置正確這個(gè)命令會(huì)有字符串response)
2.命令行指定
npm --registry https://registry.npm.taobao.org info underscore
3.編輯~/.npmrc
加入下面內(nèi)容
registry = https://registry.npm.taobao.org
搜索鏡像: https://npm.taobao.org
建立或使用鏡像,參考: https://github.com/cnpm/cnpmjs.org
在安裝過程中我自己還遇到另一個(gè)問題,錯(cuò)誤信息
gyp verb check python checking for Python executable "python2" in the PATH
gyp verb `which` failed Error: not found: python2
解決辦法: $ npm install python
在我們使用這個(gè)插件之前,我們必須像使用gulp那樣先把gulp-sass
從node_modules
文件夾中require
進(jìn)來。
var gulp = require('gulp');
// Requires the gulp-sass pluginvar
sass = require('gulp-sass');
現(xiàn)在就可以把上面的aGulpPlugin()
替換成sass()
。
gulp.task('sass', function(){
return gulp.src('source-files')
.pipe(sass()) // Using gulp-sass
.pipe(gulp.dest('destination'))
});
現(xiàn)在我們需要為sass
任務(wù)提供源文件和目標(biāo)文件來使得任務(wù)運(yùn)行,現(xiàn)在在app/scss創(chuàng)建一個(gè)styles.scss文件。這個(gè)文件將會(huì)在gulp.src()
中被添加到sass任務(wù)。
這里我們把最終的styles.css
文件輸出到app/css
文件夾中,這將是gulp.dest
的destination
。
gulp.task('sass', function(){
return gulp.src('app/scss/styles.scss')
.pipe(sass()) // Converts Sass to CSS with gulp-sass
.pipe(gulp.dest('app/css'))
});
為了測(cè)試sass任務(wù)是否像我們希望的那樣運(yùn)行。我們會(huì)在styles.scss
中添加一個(gè)Sass方法。
// styles.scss
.testing { width: percentage(5/7);}
在控制臺(tái)上運(yùn)行gulp sass
后,回到app/css
目錄下,可以看到一個(gè)styles.css
的文件。內(nèi)容如下:
.testing {
width: 71.42857%; }
PS:Gulp-sass使用LibSass把Sass轉(zhuǎn)換成CSS,這會(huì)比基于Ruby的方法更快。如果你想依然在gulp中使用Ruby方法的話,可以使用gulp-ruby-sass或者gulp-compass.
Node中的通配符(Globbing in Node)
Globs(通配符)是允許你添加多于一個(gè)文件進(jìn)gulp.src()
中的匹配模式。這就像是普通的表達(dá)式,但是是專門給文件路徑的。
當(dāng)你使用一個(gè)glob的時(shí)候,電腦會(huì)根據(jù)特定的模式來檢查你的文件名稱和路徑。若存在,則該文件就被匹配。
多數(shù)Gulp工作流通常只需要4種不同的通配符模式:
- ** *.scss: **
*
是一個(gè)在當(dāng)前目錄匹配所有模式的通配符。在這個(gè)栗子中,我們會(huì)匹配在根目錄(project)下所有以.scss
結(jié)尾的文件。 - ******/*.scss :這是一個(gè)
*
模式更為極端的一個(gè)版本,它會(huì)匹配根目錄和其它子目錄的所有以.scss
結(jié)尾的文件。 -
!not-me.scss:
!
表明Gulp會(huì)排除掉與之匹配的模式,這在你需要排除掉一個(gè)匹配中的文件時(shí)會(huì)很有用。在這個(gè)栗子中,not-me.scss
將會(huì)在匹配中被排除。 - ** .+(scss|sass) :*加號(hào)和括號(hào)(parentheses)允許Gulp去匹配多種模式,不同的模式被
|
(pipe)隔開。這個(gè)栗子中,Gulp會(huì)匹配根目錄下所有以.scss
或.sass
結(jié)尾的所有文件。
現(xiàn)在我們就可以把app/scss/styles.scss
替換成scss/**/*.scss
模式。
gulp.task('sass', function() {
return gulp.src('app/scss/**/*.scss') // Gets all files ending with .scss in app/scss and children dirs
.pipe(sass())
.pipe(gulp.dest('app/css'))
})
現(xiàn)在的問題是,我們每次都要手動(dòng)去調(diào)用gulp sass
才能把Sass轉(zhuǎn)換成CSS嗎?
我們可以通過一個(gè)叫"watching"的進(jìn)程讓Gulp在sass保存的時(shí)候自動(dòng)運(yùn)行sass任務(wù)。
監(jiān)視Sass文件的變化(Watching Sass files for changes)
Gulp為我們提供了一個(gè)watch
方法來檢查文件是否被保存。watch
方法的語(yǔ)法是:
// Gulp watch syntax
gulp.watch('files-to-watch', ['tasks', 'to', 'run']);
如果我們想監(jiān)聽所有的Sass文件并在Sass文件被保存時(shí)運(yùn)行sass任務(wù),我們只需要把files-to-watch
換成app/scss/**/*.scss
,把['tasks', 'to', 'run']
換成['sass']:
// Gulp watch syntax
gulp.watch('app/scss/**/*.scss', ['sass']);
gulp.task('watch', function(){
gulp.watch('app/scss/**/*.scss', ['sass']);
// Other watchers
})
當(dāng)你運(yùn)行了gulp watch
命令后,你就能看到Gulp馬上開始監(jiān)聽了。

接下來就看看怎么讓Gulp在Browser Sync的幫助下實(shí)現(xiàn)在保存一個(gè).scss文件時(shí)重載瀏覽器。
Browser Sync實(shí)時(shí)重載(Live-reloading with Browser Sync)
通過spinning up web 服務(wù)器Browser Sync能讓我們更容易實(shí)現(xiàn)實(shí)時(shí)重載。它也有其它特征,例如多設(shè)備同步動(dòng)作。
- 第一步:安裝Browser Sync
$ npm install browser-sync --save-dev
你會(huì)發(fā)現(xiàn)當(dāng)我們安裝Browser Sync時(shí)并沒有gulp-
前綴。這是因?yàn)锽rowser Sync是和Gulp一起運(yùn)行的,所以我們不需要使用插件。
- 第二步:require Browser Sync
var browserSync = require('browser-sync').create();
我們需要?jiǎng)?chuàng)建一個(gè)browserSync
任務(wù)使得Gulp可以使用Browser Sync旋轉(zhuǎn)加速服務(wù)器。因?yàn)槲覀冋谶\(yùn)行服務(wù)器,我們需要讓Browser Sync知道服務(wù)器的根目錄應(yīng)該在哪里。在這里的栗子中是app
文件夾:
gulp.task('browserSync', function() {
browserSync.init({
server: {
baseDir: 'app'
},
})
})
- 第三步:同時(shí)我們需要對(duì)我們的
sass
任務(wù)做一點(diǎn)小小的改變,使得Browser Sync可以在sass
任務(wù)完成時(shí)向?yàn)g覽器注入新的CSS樣式(更新CSS)。
gulp.task('sass', function() {
return gulp.src('app/scss/**/*.scss') // Gets all files ending with .scss in app/scss
.pipe(sass())
.pipe(gulp.dest('app/css'))
.pipe(browserSync.reload({
stream: true
}))
});
到這里,我們就配置好Browser Sync了。現(xiàn)在我們需要同時(shí)運(yùn)行watch
和browserSync
任務(wù)來做到實(shí)時(shí)重載。
如果我們要打開兩個(gè)命令行窗口來分別運(yùn)行gulp browserSync
和 gulp watch
這會(huì)十分笨重,所以我們要讓Gulp同時(shí)運(yùn)行它們,通過告訴watch
任務(wù)browserSync
必須在watch
運(yùn)行前完成。
可以通過給watch
任務(wù)添加第二個(gè)參數(shù)來實(shí)現(xiàn)上述要求。
gulp.task('watch', ['array', 'of', 'tasks', 'to', 'complete','before', 'watch'], function (){
// ...
})
在這個(gè)栗子中,我們會(huì)添加browerSync任務(wù)。
gulp.task('watch', ['browserSync'], function (){
gulp.watch('app/scss/**/*.scss', ['sass']);
// Other watchers
})
同時(shí),我們也要保證sass
在watch
前運(yùn)行,以保證CSS在我們運(yùn)行Gulp命令時(shí)都是最新的。
gulp.task('watch', ['browserSync', 'sass'], function (){
gulp.watch('app/scss/**/*.scss', ['sass']);
// Other watchers
});
現(xiàn)在,當(dāng)在命令行運(yùn)行了gulp watch
,Gulp會(huì)馬上啟動(dòng)sass
和'browserSync'。當(dāng)兩個(gè)任務(wù)都完成時(shí),watch
才會(huì)運(yùn)行。同時(shí)會(huì)打開app/index.html
。
如果你改變了styles.scss
文件,你會(huì)看到瀏覽器自動(dòng)的重載。

既然我們已經(jīng)監(jiān)視了.scss
文件重載,接下來我們看看怎么在HTML或JS文件被保存的時(shí)候重載瀏覽器。
可以通過添加兩個(gè)監(jiān)聽程序,并在文件被保存的時(shí)候調(diào)用browserSync.reload方法。
gulp.task('watch', ['browserSync', 'sass'], function (){
gulp.watch('app/scss/**/*.scss', ['sass']);
// Reloads the browser whenever HTML or JS files change
gulp.watch('app/*.html', browserSync.reload);
gulp.watch('app/js/**/*.js', browserSync.reload); });
目前我們已經(jīng)解決了三件事情:
- 為開發(fā)旋轉(zhuǎn)加速web服務(wù)器
- 使用Sass預(yù)處理器
- 在文件被保存時(shí)重載瀏覽器
接下來會(huì)處理優(yōu)化資源的部分。首先沖優(yōu)化CSS和JS文件開始。
優(yōu)化CSS和JS文件
在為產(chǎn)品優(yōu)化CSS和JS文件的時(shí)候,開發(fā)人員通常有兩個(gè)任務(wù)要完成:縮小(minification)和串聯(lián)(concatenation)。
- 其中一個(gè)開發(fā)人員面臨的問題就是,在使程序自動(dòng)化的時(shí)候很難把你的腳本按正確的順序串聯(lián)。
假設(shè)index.html
中包含3個(gè)<script>標(biāo)簽
<body>
<!-- other stuff -->
<script src="js/lib/a-library.js"></script>
<script src="js/lib/another-library.js"></script>
<script src="js/main.js"></script>
</body>
這些腳本被保存在兩個(gè)不同的目錄。使用傳統(tǒng)的像gulp-concatenate
這樣的傳統(tǒng)插件很難做到把他們串聯(lián)在一起。
慶幸的是,gulp-useref
可以解決這個(gè)問題。
Gulp-useref通過尋找一個(gè)以開始,以
結(jié)尾的注釋來做到把任意數(shù)目的CSS和JS文件串聯(lián)到一個(gè)文件中。
<!-- build:<type> <path> -->
... HTML Markup, list of script / link tags.
<!-- endbuild -->
<type>可以是js,css或者是remove。最好給你想要串聯(lián)的文件的類型設(shè)定
type
。如果你給設(shè)定remove
設(shè)定了type
,那么Gulp會(huì)移除整個(gè)build塊而不創(chuàng)建一個(gè)文件。
<path>在這里指的是生成文件的目標(biāo)路徑。
這里我們希望最終的JS文件被生成到j(luò)s文件夾,像main.min.js
。
<!--build:js js/main.min.js -->
<script src="js/lib/a-library.js"></script>
<script src="js/lib/another-library.js"></script>
<script src="js/main.js"></script>
<!-- endbuild -->
現(xiàn)在配置gulp-useref插件到gulpfile。我們需要安裝并把它require到gulpfile。
$ npm install gulp-useref --save-dev
var useref = require('gulp-useref');
構(gòu)建useref任務(wù)的步驟和其它相似。
gulp.task('useref', function(){
return gulp.src('app/*.html')
.pipe(useref())
.pipe(gulp.dest('dist'))
});
運(yùn)行gulp useref
就可以了。
然而,文件現(xiàn)在還沒有被縮小。我們需要使用gulp-uglify
插件來縮小JS文件。同時(shí),我們還需要另一個(gè)叫gulp-if
插件來保證我們只會(huì)縮小JS文件。
// Other requires...
var uglify = require('gulp-uglify');
var gulpIf = require('gulp-if');
gulp.task('useref', function(){
return gulp.src('app/*.html')
.pipe(useref()) // Minifies only if it's a JavaScript file
.pipe(gulpIf('*.js', uglify()))
.pipe(gulp.dest('dist'))
});
其它的同理。
在串聯(lián)CSS文件的時(shí)候,使用gulp-cssnano
來壓縮文件。
var cssnano = require('gulp-cssnano');
gulp.task('useref', function(){
return gulp.src('app/*.html')
.pipe(useref())
.pipe(gulpIf('*.js', uglify())) // Minifies only if it's a CSS file
.pipe(gulpIf('*.css', cssnano()))
.pipe(gulp.dest('dist'))
});
優(yōu)化圖片
使用gulp-imagemin
來優(yōu)化圖片。
通過gulp-imagemin
我們可以壓縮png
,jpg
,gif
甚至svg
。
gulp.task('images', function(){
return gulp.src('app/images/**/*.+(png|jpg|gif|svg)')
.pipe(imagemin())
.pipe(gulp.dest('dist/images'))
});
因?yàn)椴煌奈募愋投紩?huì)被不同程度的優(yōu)化,你可能會(huì)想給imagemin
增加可選參數(shù)來定制每個(gè)文件的優(yōu)化方式。
例如,你可以通過設(shè)置interlaced
參數(shù)為true
來創(chuàng)建隔行的GIFs。
gulp.task('images', function(){
return gulp.src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
.pipe(imagemin({
// Setting interlaced to true
interlaced: true
}))
.pipe(gulp.dest('dist/images'))
});
優(yōu)化圖片是一項(xiàng)十分慢的過程,除非必須,否則你不會(huì)想要重復(fù)一遍。這樣我們可以使用
gulp-cache
插件。
var cache = require('gulp-cache');
gulp.task('images', function(){
return gulp.src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
// Caching images that ran through imagemin
.pipe(cache(imagemin({
interlaced: true
})))
.pipe(gulp.dest('dist/images'))
});
現(xiàn)在還有一個(gè)需要從app
目錄轉(zhuǎn)換到dist
目錄,就是fonts 目錄。
復(fù)制fonts到Dist
因?yàn)閒ont文件已經(jīng)被優(yōu)化過了,所以我們不需要再做什么。只需要把fonts復(fù)制到dist就好了。
我們可以通過指明gulp.src
和gulp.dest
來復(fù)制文件,不需要其它插件。
gulp.task('fonts', function() {
return gulp.src('app/fonts/**/*')
.pipe(gulp.dest('dist/fonts'))
})
這樣子,當(dāng)你運(yùn)行gulp fonts
時(shí),Gulp就會(huì)從app
復(fù)制fonts
到dist
。
到現(xiàn)在,我們已經(jīng)有了6個(gè)不同的任務(wù),每個(gè)都要在命令行單獨(dú)調(diào)用,這真是一件很笨重的事。因此我們想要把所有命令綁定到一句命令中。
在我們那樣做之前,我們先來看看怎么自動(dòng)清理產(chǎn)生的文件。
自動(dòng)清理產(chǎn)生的文件
我們已經(jīng)可以自動(dòng)創(chuàng)建文件,我們想要確保那些不會(huì)再使用的文件不會(huì)留再任何我們不知道的地方。
這個(gè)過程叫做cleaning(或刪除文件)。
我們使用del
來清理文件。
引用方式同上述的其它一樣。
npm install del --save-dev
var del = require('del');
del
方法需要在一個(gè)告訴它要?jiǎng)h除哪個(gè)文件的node通配符數(shù)組。
配置一個(gè)Gulp任務(wù)就跟第一個(gè)'hello'任務(wù)差不多。
gulp.task('clean:dist', function() {
return del.sync('dist');
})
這樣,當(dāng)你運(yùn)行g(shù)ulp clean:dist時(shí),'dist'文件夾就會(huì)被刪除。
我們不需要擔(dān)心會(huì)刪除了
dist/images
文件夾,因?yàn)?code>gulp-cache已經(jīng)把圖片緩存在本地系統(tǒng)中了。要想清楚本地系統(tǒng)緩存,你可以單獨(dú)創(chuàng)建一個(gè)叫cache:clear
的任務(wù)。
gulp.task('cache:clear', function (callback) {
return cache.clearAll(callback)
})
結(jié)合Gulp任務(wù)
先來總結(jié)下我們做過的東西,目前,我們已經(jīng)創(chuàng)建了兩類Gulp任務(wù)。
第一種是用于開發(fā)過程的,把Sass編譯成CSS,監(jiān)視變化,有依據(jù)地重載瀏覽器。
第二種適用于優(yōu)化過程的,為產(chǎn)品網(wǎng)站準(zhǔn)備好所有文件。優(yōu)化這個(gè)過程中像CSS,JS和圖片這樣子的資源,把fonts從app
復(fù)制到dist
。
我們之前已經(jīng)用gulp watch
命令把第一種任務(wù)組合到一個(gè)工作流中。
gulp.task('watch', ['browserSync', 'sass'], function (){
// ... watchers
})
第二類包括那些我們運(yùn)行來創(chuàng)造產(chǎn)品網(wǎng)站的任務(wù)。其中包括了clean:dist
,sass
,useref
,images
和fonts
.
我們需要一個(gè)額外的叫Run Sequence
的插件。
$ npm install run-sequence --save-dev
語(yǔ)法:
var runSequence = require('run-sequence');
gulp.task('task-name', function(callback) {
runSequence('task-one', 'task-two', 'task-three', callback);
});
這樣任務(wù)就會(huì)一個(gè)接一個(gè)的運(yùn)行。
Run Sequence也允許你同時(shí)運(yùn)行任務(wù),只要你把他們放到一個(gè)數(shù)組中。
gulp.task('task-name', function(callback) {
runSequence('task-one', ['tasks','two','run','in','parallel'], 'task-three', callback);
});
為了一致性,我們也可以用相同的序列處理第一組任務(wù)。并且使用default
作為任務(wù)名字。應(yīng)為這樣我們?cè)谡{(diào)用的時(shí)候只需要寫gulp
。
作者的一些建議:
For development:
Using Autoprefixer to write vendor-free CSS code
Adding Sourcemaps for easier debugging
Creating Sprites with sprity
Compiling only files that have changed with gulp-changed
Writing ES6 with Babel or Traceur
Modularizing Javascript files with Browserify, webpack, or jspm
Modularizing HTML with template engines like Handlebars or Swig
Splitting the gulpfile into smaller files with require-dir
Generating a Modernizr script automatically with gulp-modernizr
For optimization:
Removing unused CSS with unCSS
Further optimizing CSS with CSSO
Generating inline CSS for performance with Critical
總結(jié):
這篇文章的內(nèi)容可以幫助我們快速的了解Gulp,但是其中還有很多問題需要深究,還要繼續(xù)學(xué)習(xí)。
PS:評(píng)論區(qū)里有人提到不應(yīng)該使用sudo。
相關(guān)閱讀:
https://pawelgrzybek.com/fix-priviliges-and-never-again-use-sudo-with-npm/
https://github.com/brock/node-reinstall