你是否還在手動壓縮圖片橙数、js、css帅戒?
是否還在手動編譯sass灯帮、less、coffee逻住?
是否還在手動合并精靈圖钟哥?
是否還在
……
簡單點,干活的方式簡單點鄙信。
在前端技術(shù)飛速發(fā)展的今天瞪醋,如果你還在日復(fù)一日地重復(fù)著那無休止的體力勞動,那你要反思了装诡,這年頭不懂搗鼓個工作流出來都不好意思說自己學(xué)前端的银受。
畢竟javascript是最好的編程語言嘛。:)
玩笑開到這里鸦采,到底什么是工作流宾巍?
工作流(Workflow),指“業(yè)務(wù)過程的部分或整體在計算機(jī)應(yīng)用環(huán)境下的自動化”渔伯。 ——百度百科
如題顶霞,此次的分享主題"自動化構(gòu)建",目的便是介紹如何根據(jù)需要搭建一套工作流锣吼,由淺入深选浑。
一简肴、前提
往下看之前温数,請自備以下知識:
- nodejs的一些基礎(chǔ)用法,比如:node index.js臼闻;
- npm的基本用法init读恃、(un)install隧膘、run等;
- 了解gulp寺惫、webpack(本章不會用到)的用途
以上知識點請務(wù)必掌握疹吃,不然沒法接著往下講,切勿好高騖遠(yuǎn)西雀;
如果你準(zhǔn)備好了萨驶,那我們就開始了!
我們先來設(shè)定一個簡單的需求:
- 一個本地開發(fā)環(huán)境艇肴,具備監(jiān)控文件變化并實時更新的功能篡撵;
- 修改代碼判莉,保存之后瀏覽器自動刷新
- 實時編譯各種預(yù)編譯格式文件
- 壓縮合并靜態(tài)資源,打包輸出
- 部署上傳
一言不合就上圖:
好育谬,有了這個規(guī)劃之后券盅,我們開始著手來實現(xiàn)它。
二膛檀、源碼
首先新建一個項目文件夾project锰镀,接著我們打開命令行工具,切換到這個目錄下咖刃,開始初始化這個項目:
cd project
npm init
按照提示完成初始化泳炉,打開項目我們會得到一個package.json文件像這樣:
// package.json
{
"name": "project",
"version": "1.0.0",
"description": "a test project",
"main": "index.js",
"scripts": {
"test": "node ./build/test.js"
},
"author": "jack lo",
"license": "ISC"
}
緊接著我們先來創(chuàng)造一份源碼,結(jié)構(gòu)如下:
- project
|- src // 源文件夾
| |- tpl
| | `- index.html
| |- css
| | `- index.css
| |- js
| | `- index.js
|- dist // 打包文件夾
`- package.json
三嚎杨、編譯打包
一切材料就緒花鹅,我們開始安裝工具包,這一次我們主要使用gulp來構(gòu)建枫浙,不需要用到webpack刨肃,同時,為了實現(xiàn)瀏覽器自動刷新的功能箩帚,我們還需要用到browser-sync真友。
npm install gulp browser-sync --save-dev
這需要花點時間。
---------- 虛度光陰中 ---------
事實上紧帕,這里有個小坑需要我們提前準(zhǔn)備一下盔然,因為我們接下來會用到gulp的cli,所以這里我們需要全局安裝gulp是嗜,沒錯愈案,我就是設(shè)套讓你裝兩遍!
這是個好習(xí)慣鹅搪,請務(wù)必以后也堅持這么做站绪。把開發(fā)時候用到的所有npm包都記錄在package.json文件中,可以方便日后自己以及他人的使用涩嚣。
npm install gulp -g
mac用戶可能還要sudo一下:
sudo npm install gulp -g
回車后按提示輸入密碼崇众,繼續(xù)回車掂僵,就可以正常安裝了航厚。
---------- 再一次虛度光陰中 ---------
ok,安裝完后锰蓬,我們可以開始做點有意義的事了幔睬。
gulp的用法其實相當(dāng)簡單,api也就那么幾個芹扭。簡單來說麻顶,就是寫好一個配置文件gulpfile.js赦抖,然后在命令行里執(zhí)行它。
首先在根目錄下新建一個gulpfile.js辅肾,然后隨便建一個叫做test的任務(wù):
// gulpfile.js
var gulp = require('gulp')
// 創(chuàng)建一個名為test的任務(wù)队萤,任務(wù)內(nèi)容只是簡單地在控制臺輸出一段文本
gulp.task('test', function () {
return console.log('this is a test')
})
ok,保存矫钓。然后我們回到命令行要尔,輸入以下命令然后回車
gulp test
gulp后面跟的是你所要執(zhí)行的任務(wù)名,于是我們得到了這樣一個結(jié)果
[22:56:35] Using gulpfile ~/project/gulpfile.js
[22:56:35] Starting 'test'...
this is a test
[22:56:35] Finished 'test' after 149 μs
成功輸出文本新娜!恭喜赵辕,你已經(jīng)掌握了gulp一半以上的用法。
現(xiàn)在我們嘗試著來操作文件概龄,我們將src/html里面的html文件給拷貝到dist文件夾(如果不存在則新建)里面去还惠,怎么做?
// 創(chuàng)建一個copy的任務(wù)
gulp.task('copy', function () {
return gulp.src('src/tpl/*.html')
.pipe(gulp.dest('dist'))
})
這里的pipe其實是一種管道私杜,你可以一直pipe連著pipe下去蚕键,也就是這個工作流可以一直這樣一層層執(zhí)行下去,隨便你定義多少個處理任務(wù)都行歪今,這就是gulp的特點嚎幸,簡單明了的工作流程。很多人不明白gulp到底是做什么的寄猩,其實到這里嫉晶,就可以有個大概的認(rèn)識了:
gulp是以定義并執(zhí)行一個個任務(wù)的形式來工作的流程管理工具,它的作用在于提供一套簡單易用的工作方式田篇。
在gulp以前替废,處理一份sass文件,你可能需要先執(zhí)行一次編譯的任務(wù)泊柬,編譯成css之后椎镣,再執(zhí)行一遍壓縮css的任務(wù),壓縮完之后兽赁,再手動拷貝到打包文件夾里状答。原本需要分三步來操作的一件事情,在gulp里面就是一個任務(wù)的sei而已:
// 創(chuàng)建一個css的處理任務(wù)
gulp.task('sass', function () {
return gulp.src('src/sass/*.scss')
.pipe(sass())
.pipe(minifycss())
.pipe(gulp.dest('dist/static'))
})
甚至我們可以同時執(zhí)行多個任務(wù)刀崖,我可以定義好coffee惊科、sass、image亮钦、html的四個任務(wù)馆截,再把他們合并到一個任務(wù)當(dāng)中去,一次性執(zhí)行完:
// 創(chuàng)建一個build的處理任務(wù)
gulp.task('build', ['coffee', 'sass', 'image', 'html'])
我只需要:
gulp build
回車,這酸爽蜡娶。
好了混卵,介紹了這么多內(nèi)容,最后我們還是回到最開始那份需求的實現(xiàn)上來窖张。
我們來建幾個任務(wù)幕随,分別處理js、css宿接、html文件合陵,把js和css文件放到dist/static目錄下,把html文件放到dist下:
gulp.task('css', function () {
return gulp.src('src/css/*.css')
.pipe(gulp.dest('dist/static'))
})
gulp.task('js', function () {
return gulp.src('src/js/*.js')
.pipe(gulp.dest('dist/static'))
})
gulp.task('html', function () {
return gulp.src('src/tpl/*.html')
.pipe(gulp.dest('dist'))
})
gulp.task('build', ['css', 'js', 'html'])
命令行g(shù)ulp build一下澄阳,回車拥知!再看看dist文件夾,done碎赢!
咋一看低剔,好像沒什么問題,但好像又有哪里不太對勁肮塞。不對啊襟齿,除了復(fù)制到dist文件夾,好像沒啥功能啊枕赵,說好的壓縮合并呢猜欺?說好的處理預(yù)編譯呢?嗯拷窜,有了這個框架开皿,這些功能我們想加多少加多少。
這樣簡單的項目篮昧,我們沒法玩出花樣赋荆,我們來點預(yù)編譯語言,css用sass代替懊昨,html用swig代替:
- project
|- src // 源文件夾
| |- tpl
| | `- index.swig
| |- sass
| | `- index.scss
| |- js
| | `- index.js
|- dist // 打包文件夾
`- package.json
編譯sass需要安裝gulp-sass模塊窄潭,編譯swig需要gulp-swig。注意到了嗎酵颁?基本上gulp的模塊都以gulp-*
的形式出現(xiàn)嫉你,所以如果以后你使用gulp的時候想用什么模塊,可以試試在npm搜gulp-模塊名
躏惋。
npm install gulp-sass gulp-swig --save-dev
---------- 虛度光陰中 ---------
安裝完后幽污,我們再來修改一下配置文件gulpfile.js
var gulp = require('gulp')
var sass = require('gulp-sass')
var swig = require('gulp-swig')
gulp.task('sass', function () {
return gulp.src('src/sass/*.scss')
.pipe(sass({
outputStyle: 'compressed' // 此配置使文件編譯并輸出壓縮過的文件
}))
.pipe(gulp.dest('dist/static'))
})
gulp.task('js', function () {
return gulp.src('src/js/*.js')
.pipe(gulp.dest('dist/static'))
})
gulp.task('tpl', function () {
return gulp.src('src/tpl/*.swig')
.pipe(swig({
defaults: {
cache: false // 此配置強(qiáng)制編譯文件不緩存
}
}))
.pipe(gulp.dest('dist'))
})
gulp.task('build', ['sass', 'js', 'tpl'])
再接著gulp build一下,編譯結(jié)束其掂,可以看看dist下是不是有index.html油挥,dist/static下是不是有編譯完成且壓縮過的index.css潦蝇,沒有你找我款熬!這里就不分別展示源文件和打包后文件的內(nèi)容了深寥,因為并不重要,想看演示項目內(nèi)容的朋友贤牛,可以在文章結(jié)尾處找到本次演示項目的git倉庫鏈接惋鹅。各種預(yù)編語言和前端模板大家可以根據(jù)自己的喜好選擇,筆者只是選擇自己熟悉的幾種來做演示殉簸。
到這里我們基本上已經(jīng)完成打包的工作了闰集,我們來試著搭建開發(fā)環(huán)境。
四般卑、搭建開發(fā)環(huán)境
前文我們提到一個工具browser-sync武鲁,還記得嗎?現(xiàn)在用得上了蝠检!我們先創(chuàng)建一個開發(fā)任務(wù)沐鼠,像gulp build一樣簡單的任務(wù),我們的目標(biāo)是:沒有蛀牙 一句話搞定叹谁。
gulp.task('dev', ['js:dev', 'sass:dev', 'tpl:dev'], function () {
// do something here...
})
看著好像稍有不同饲梭。這一次我們創(chuàng)建一個叫做dev的任務(wù),這個任務(wù)先執(zhí)行js:dev
焰檩、sass:dev
和tpl:dev
三個任務(wù)憔涉,然后再執(zhí)行回調(diào)里的內(nèi)容,我們的本地服務(wù)器就是要在回調(diào)里去定義并且啟動析苫。
在這之前兜叨,我們先來了解一下browser-sync:
Browsersync能讓瀏覽器實時、快速響應(yīng)您的文件更改(html衩侥、js浪腐、css、sass顿乒、less等)并自動刷新頁面议街。 ——Browsersync中文網(wǎng)
事實上Browsersync可以理解為一個本地服務(wù)器,類似于Apache璧榄,不同的是特漩,Browsersync只提供很簡單的http功能,它的主要功能骨杂,是通過搭建一個本地服務(wù)器涂身,并且監(jiān)聽文件的更改,自動刷新瀏覽器搓蚪,實時地呈現(xiàn)最新內(nèi)容蛤售。
browser-sync的用法:
var browserSync = require('browser-sync').create()
browserSync.init({
server: {
baseDir: "./" // 設(shè)置服務(wù)器的根目錄
}
})
其實也很簡單,沒有太多內(nèi)容,現(xiàn)在我們要將它整合進(jìn)我們的gulp任務(wù)里悴能,搗鼓幾下揣钦,我們得到:
gulp.task('dev', ['js:dev', 'sass:dev', 'tpl:dev'], function () {
browserSync.init({
server: {
baseDir: "./dist" // 設(shè)置服務(wù)器的根目錄為dist目錄
},
notify: false // 開啟靜默模式
})
// 我們使用gulp的文件監(jiān)聽功能,來實時編譯修改過后的文件
gulp.watch('src/js/*.js', ['js:dev'])
gulp.watch('src/sass/*.scss', ['sass:dev'])
gulp.watch('src/tpl/*.swig', ['tpl:dev'])
})
這里的watch行為可以簡單理解為:我(gulp)就這么盯著你(src/js/*.js文件)漠酿,只要你被改動了冯凹,我就馬上執(zhí)行js:dev
任務(wù)來處理你,產(chǎn)生最新的文件炒嘲。
那么宇姚,我們現(xiàn)在還需要補(bǔ)充一下js:dev
、sass:dev
和tpl:dev
這三個任務(wù)夫凸,與原來的js
浑劳、sass
和tpl
三個任務(wù)大同小異,這里籠統(tǒng)地過一遍就好夭拌,我們直接看代碼:
var browserSync = require('browser-sync').create()
var reload = browserSync.reload
gulp.task('sass:dev', function () {
return gulp.src('src/sass/*.scss')
.pipe(sass())
.pipe(gulp.dest('dist/static'))
.pipe(reload({stream: true}))
})
gulp.task('js:dev', function () {
return gulp.src('src/js/*.js')
.pipe(gulp.dest('dist/static'))
.pipe(reload({stream: true}))
})
gulp.task('tpl:dev', function () {
return gulp.src('src/tpl/*.swig')
.pipe(swig({
defaults: {
cache: false // 此配置強(qiáng)制編譯文件不緩存
}
}))
.pipe(gulp.dest('dist'))
.pipe(reload({stream: true}))
})
這里的reload
方法呀洲,我們不需要過多了解,只要知道啼止,通過執(zhí)行它就可以刷新瀏覽器。這里的stream
用法可以查閱官方文檔献烦,這里不重要所以不細(xì)講。
值得留意的是:這里的
sass:dev
任務(wù)巩那,我們并沒有像sass
任務(wù)一樣配置編譯模式為compressed,也就是不使用壓縮功能即横,為什么呢噪生?事實上,我們在開發(fā)的時候跺嗽,并不需要壓縮靜態(tài)資源文件,可以說我們不在意它的體積是大一點還是小一點桨嫁,我們在意的是樣式是否寫得符合期望,我們在乎的是功能是否實現(xiàn)份帐,所以不需要啟用壓縮或者其他的什么優(yōu)化功能璃吧,這樣可以減輕編譯的負(fù)擔(dān),加快編譯速度废境。如果你對js或者圖片也使用了壓縮功能筒繁,建議在開發(fā)模式下去掉巴元,只在打包模式下使用。
最終我們整理得到一份gulpfile.js文件:
var gulp = require('gulp')
var sass = require('gulp-sass')
var swig = require('gulp-swig')
var browserSync = require('browser-sync').create()
var reload = browserSync.reload
gulp.task('sass', function () {
return gulp.src('src/sass/*.scss')
.pipe(sass({
outputStyle: 'compressed' // 此配置使文件編譯并輸出壓縮過的文件
}))
.pipe(gulp.dest('dist/static'))
})
gulp.task('js', function () {
return gulp.src('src/js/*.js')
.pipe(gulp.dest('dist/static'))
})
gulp.task('tpl', function () {
return gulp.src('src/tpl/*.swig')
.pipe(swig({
defaults: {
cache: false // 此配置強(qiáng)制編譯文件不緩存
}
}))
.pipe(gulp.dest('dist'))
})
gulp.task('sass:dev', function () {
return gulp.src('src/sass/*.scss')
.pipe(sass())
.pipe(gulp.dest('dist/static'))
.pipe(reload({stream: true}))
})
gulp.task('js:dev', function () {
return gulp.src('src/js/*.js')
.pipe(gulp.dest('dist/static'))
.pipe(reload({stream: true}))
})
gulp.task('tpl:dev', function () {
return gulp.src('src/tpl/*.swig')
.pipe(swig({
defaults: {
cache: false // 此配置強(qiáng)制編譯文件不緩存
}
}))
.pipe(gulp.dest('dist'))
.pipe(reload({stream: true}))
})
gulp.task('dev', ['js:dev', 'sass:dev', 'tpl:dev'], function () {
browserSync.init({
server: {
baseDir: "./dist"
},
notify: false
})
gulp.watch('src/js/*.js', ['js:dev'])
gulp.watch('src/sass/*.scss', ['sass:dev'])
gulp.watch('src/tpl/*.swig', ['tpl:dev'])
})
gulp.task('build', ['sass', 'js', 'tpl'])
嗯,完美幻赚,到這一步,我們這個自動化構(gòu)建已經(jīng)基本完成了落恼,而且還算是完整。現(xiàn)在佳谦,我們開發(fā)的時候,就執(zhí)行g(shù)ulp dev啥刻,打包的時候就執(zhí)行g(shù)ulp build,是不是很方便可帽?
注意:gulp dev任務(wù)啟動以后是一直保持工作狀態(tài)的窗怒,也就是它不像gulp build一樣一次性執(zhí)行完映跟,它是keep alive的扬虚,所以我們?nèi)绻V惯@個任務(wù),需要手動按ctrl+c組合鍵辜昵,結(jié)束這個任務(wù)。
五贷洲、補(bǔ)充
然而事情還沒完,我們的目標(biāo)是:裝逼 盡善盡美优构!
有三個地方其實我們還沒做到位:
- 對命令進(jìn)行包裝雁竞,盡量簡潔钦椭。gulp dev和gulp build其實已經(jīng)很簡潔了,但事實上這只是因為這個項目很簡單侥锦,用到的命令很少,在開發(fā)復(fù)雜的項目時恭垦,我們通常要輸入復(fù)雜的一長串的命令行格嗅。gulp dev其實也是gulp --gulpfile gulpfile.js dev的缺省寫法而已番挺,具體情況可以去gulp官網(wǎng)了解。所以我們需要有更簡潔的方式去執(zhí)行這些預(yù)先準(zhǔn)備好的腳本屯掖,就好像windows系統(tǒng)下的快捷方式玄柏;
- 目前的情況是,我們每次執(zhí)行贴铜,都會將文件拷貝到dist目錄下粪摘,但是卻沒有刪除的工作,也就是說文件數(shù)量只增不減绍坝,這樣多次下來徘意,隨著文件的增刪改動,必然會遺留很多沒有用的文件轩褐。我們需要每次啟動編譯或者打包之前,先把整個dist文件夾刪除邑退,然后再重新生成dist劳澄;
- 上傳的功能還沒做呢?秒拔!
二話不說就開工!
第一點很好解決作谚,我們可以把腳本作為一項配置存放在package.json文件中:
{
"name": "project",
"version": "1.0.0",
"description": "a test project",
"main": "index.js",
"scripts": {
"dev": "gulp dev", // 開發(fā)腳本
"build": "gulp build", // 打包腳本
"test": "node ./build/test.js"
},
"author": "jack lo",
"license": "ISC",
"devDependencies": {
"browser-sync": "^2.13.0",
"gulp": "^3.9.1",
"gulp-sass": "^2.3.2",
"gulp-swig": "^0.8.0"
}
}
注意到了嗎庵芭?scripts項就是用來預(yù)定義腳本的地方,我們可以很方便地把腳本按照上面的形式封裝好双吆,然后執(zhí)行的方式就變成了:
npm run dev // 執(zhí)行開發(fā)
npm run build // 執(zhí)行打包
搞定会前!
我們接著看第二點匾竿,刪除dist文件夾,多簡單的事傲俦印昵慌!鼠標(biāo)右鍵,刪除废离,搞定礁芦!
……
哪有這么low的事,我們的目標(biāo)是:懶癌晚期 能不自己做的事情肖方,絕不自己動手未状。
有一個叫做rimraf的包,可以幫我們做這事司草,我們需要用到它的cli,所以跟gulp一樣猜憎,我們?nèi)职惭b它:
npm install rimraf -g
安裝完后搔课,我們再重新修改一下package.json文件中的scripts內(nèi)容:
{
"name": "project",
"version": "1.0.0",
"description": "a test project",
"main": "index.js",
"scripts": {
"dev": "gulp dev", // 開發(fā)腳本
"build": "rimraf dist && gulp build", // 打包腳本
"test": "node ./build/test.js"
},
"author": "jack lo",
"license": "ISC",
"devDependencies": {
"browser-sync": "^2.13.0",
"gulp": "^3.9.1",
"gulp-sass": "^2.3.2",
"gulp-swig": "^0.8.0"
}
}
ok,現(xiàn)在試試執(zhí)行npm run build
爬泥,dist文件夾是不是先被刪除,然后再重新生成了踩官?
完美境输。
第三點放到最后才補(bǔ)充肾扰,主要是考慮到它并不是必要的蛋逾,因為有些項目并不需要ftp上傳,一般是提交svn区匣,然后再由后端或者運維去部署,筆者是需要將靜態(tài)資源上傳到cdn服務(wù)器進(jìn)行加速的莲绰,所以需要這樣一個任務(wù)姑丑,在此我們簡單介紹一下。
舉一反三一下栅哀,我們再創(chuàng)建一個upload任務(wù):
var ftp = require('gulp-ftp')
var gutil = require('gulp-util')
gulp.task('upload', function () {
return gulp.src('dist/**')
.pipe(ftp({
host: '8.8.8.8', // 遠(yuǎn)程主機(jī)ip
port: 22, // 端口
user: 'username', // 帳號
pass: 'password', // 密碼
remotePath: '/project' // 上傳路徑,不存在則新建
}))
.pipe(gutil.noop())
})
自行安裝一下gulp-ftp和gulp-util兩個包戳晌,然后在package.json文件中的scripts補(bǔ)充一個腳本npm run upload來執(zhí)行g(shù)ulp upload痴柔。
筆者通常都是打包之后順便上傳,命令行直接輸入npm run build && npm run upload咳蔚,回車,然后就可以愉快地去跟旁邊的妹紙聊天了侈询。
六堆巧、總結(jié)
到這里,我們已經(jīng)完整搭完了這一套簡易自動化工具谍肤,好像講了很多東西,其實總結(jié)起來內(nèi)容非常少:我們只不過分別用三個小任務(wù)(sass篷角、js系任、tpl)虐块,組成了build和dev這兩個大任務(wù)嘉蕾,僅此而已。熟練的情況下操作起來错忱,整個過程也不過十分鐘!
ps:網(wǎng)絡(luò)太差怪我咯儿普?呵呵掷倔。
由于時間和篇幅關(guān)系,我們只簡單處理了css勒葱、js和html,事實上错森,你還可以在這個基礎(chǔ)上繼續(xù)完善下去篮洁,js可以由coffeejs編譯得到,而且還可以繼續(xù)壓縮瓦阐,甚至可以把全部js文件合并成一個篷牌!html也一樣可以繼續(xù)壓縮。而且枷颊,你完全可以自己創(chuàng)建一個任務(wù)去處理其他諸如圖片、字體等等信卡。
好啦题造,簡易版就講到這里了。
后話:gulp與webpack都是目前比較流行的編譯打包工具界赔,那么它們之間有什么異同點牵触?怎么去進(jìn)行選擇咐低?我們下一節(jié)將介紹如何去用webpack搭建一套開發(fā)工具,從中我們可以感受這兩者的差別绰更,敬請留意锡宋。
本次演示項目的git地址:gulp_base
【下一篇:進(jìn)階:構(gòu)建具備版本管理能力的項目】
(文章有任何謬誤之處,歡迎留言指出)