入門:十分鐘自動化構建

你是否還在手動壓縮圖片、js、css?
是否還在手動編譯sass、less、coffee?
是否還在手動合并精靈圖?
是否還在
……

簡單點,干活的方式簡單點。

在前端技術飛速發(fā)展的今天,如果你還在日復一日地重復著那無休止的體力勞動,那你要反思了,這年頭不懂搗鼓個工作流出來都不好意思說自己學前端的。

畢竟javascript是最好的編程語言嘛。:)

玩笑開到這里,到底什么是工作流?

工作流(Workflow),指“業(yè)務過程的部分或整體在計算機應用環(huán)境下的自動化”。 ——百度百科

如題,此次的分享主題"自動化構建",目的便是介紹如何根據(jù)需要搭建一套工作流,由淺入深。

一、前提

往下看之前,請自備以下知識:

  • nodejs的一些基礎用法,比如:node index.js;
  • npm的基本用法init、(un)install、run等;
  • 了解gulpwebpack(本章不會用到)的用途

以上知識點請務必掌握,不然沒法接著往下講,切勿好高騖遠;
如果你準備好了,那我們就開始了!

我們先來設定一個簡單的需求:

  • 一個本地開發(fā)環(huán)境,具備監(jiān)控文件變化并實時更新的功能;
  • 修改代碼,保存之后瀏覽器自動刷新
  • 實時編譯各種預編譯格式文件
  • 壓縮合并靜態(tài)資源,打包輸出
  • 部署上傳

一言不合就上圖:


自動化構建(入門).png

好,有了這個規(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)造一份源碼,結構如下:

- project
  |- src  // 源文件夾
  | |- tpl
  | | `- index.html
  | |- css
  | | `- index.css
  | |- js
  | | `- index.js
  |- dist  // 打包文件夾
  `- package.json

三、編譯打包

一切材料就緒,我們開始安裝工具包,這一次我們主要使用gulp來構建,不需要用到webpack,同時,為了實現(xiàn)瀏覽器自動刷新的功能,我們還需要用到browser-sync

npm install gulp browser-sync --save-dev

這需要花點時間。

---------- 虛度光陰中 ---------

事實上,這里有個小坑需要我們提前準備一下,因為我們接下來會用到gulp的cli,所以這里我們需要全局安裝gulp,沒錯,我就是設套讓你裝兩遍!

這是個好習慣,請務必以后也堅持這么做。把開發(fā)時候用到的所有npm包都記錄在package.json文件中,可以方便日后自己以及他人的使用。

npm install gulp -g

mac用戶可能還要sudo一下:

sudo npm install gulp -g

回車后按提示輸入密碼,繼續(xù)回車,就可以正常安裝了。

---------- 再一次虛度光陰中 ---------

ok,安裝完后,我們可以開始做點有意義的事了。

gulp的用法其實相當簡單,api也就那么幾個。簡單來說,就是寫好一個配置文件gulpfile.js,然后在命令行里執(zhí)行它。

首先在根目錄下新建一個gulpfile.js,然后隨便建一個叫做test的任務:

// gulpfile.js
var gulp = require('gulp')

// 創(chuàng)建一個名為test的任務,任務內容只是簡單地在控制臺輸出一段文本
gulp.task('test', function () {
  return console.log('this is a test')
})

ok,保存。然后我們回到命令行,輸入以下命令然后回車

gulp test

gulp后面跟的是你所要執(zhí)行的任務名,于是我們得到了這樣一個結果

[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

成功輸出文本!恭喜,你已經掌握了gulp一半以上的用法。

現(xiàn)在我們嘗試著來操作文件,我們將src/html里面的html文件給拷貝到dist文件夾(如果不存在則新建)里面去,怎么做?

// 創(chuàng)建一個copy的任務
gulp.task('copy', function () {
  return gulp.src('src/tpl/*.html')
  .pipe(gulp.dest('dist'))
})

這里的pipe其實是一種管道,你可以一直pipe連著pipe下去,也就是這個工作流可以一直這樣一層層執(zhí)行下去,隨便你定義多少個處理任務都行,這就是gulp的特點,簡單明了的工作流程。很多人不明白gulp到底是做什么的,其實到這里,就可以有個大概的認識了:

gulp是以定義并執(zhí)行一個個任務的形式來工作的流程管理工具,它的作用在于提供一套簡單易用的工作方式。

在gulp以前,處理一份sass文件,你可能需要先執(zhí)行一次編譯的任務,編譯成css之后,再執(zhí)行一遍壓縮css的任務,壓縮完之后,再手動拷貝到打包文件夾里。原本需要分三步來操作的一件事情,在gulp里面就是一個任務的sei而已:

// 創(chuàng)建一個css的處理任務
gulp.task('sass', function () {
  return gulp.src('src/sass/*.scss')
  .pipe(sass())
  .pipe(minifycss())
  .pipe(gulp.dest('dist/static'))
})

甚至我們可以同時執(zhí)行多個任務,我可以定義好coffee、sass、image、html的四個任務,再把他們合并到一個任務當中去,一次性執(zhí)行完:

// 創(chuàng)建一個build的處理任務
gulp.task('build', ['coffee', 'sass', 'image', 'html'])

我只需要:

gulp build

回車,這酸爽。

好了,介紹了這么多內容,最后我們還是回到最開始那份需求的實現(xiàn)上來。

我們來建幾個任務,分別處理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'])

命令行gulp build一下,回車!再看看dist文件夾,done!

咋一看,好像沒什么問題,但好像又有哪里不太對勁。不對啊,除了復制到dist文件夾,好像沒啥功能啊,說好的壓縮合并呢?說好的處理預編譯呢?嗯,有了這個框架,這些功能我們想加多少加多少。

這樣簡單的項目,我們沒法玩出花樣,我們來點預編譯語言,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  // 此配置強制編譯文件不緩存
    }
  }))
  .pipe(gulp.dest('dist'))
})

gulp.task('build', ['sass', 'js', 'tpl'])

再接著gulp build一下,編譯結束,可以看看dist下是不是有index.html,dist/static下是不是有編譯完成且壓縮過的index.css,沒有你找我!這里就不分別展示源文件和打包后文件的內容了,因為并不重要,想看演示項目內容的朋友,可以在文章結尾處找到本次演示項目的git倉庫鏈接。各種預編語言和前端模板大家可以根據(jù)自己的喜好選擇,筆者只是選擇自己熟悉的幾種來做演示。

到這里我們基本上已經完成打包的工作了,我們來試著搭建開發(fā)環(huán)境。

四、搭建開發(fā)環(huán)境

前文我們提到一個工具browser-sync,還記得嗎?現(xiàn)在用得上了!我們先創(chuàng)建一個開發(fā)任務,像gulp build一樣簡單的任務,我們的目標是:沒有蛀牙 一句話搞定。

gulp.task('dev', ['js:dev', 'sass:dev', 'tpl:dev'], function () {
  // do something here...
})

看著好像稍有不同。這一次我們創(chuàng)建一個叫做dev的任務,這個任務先執(zhí)行js:dev、sass:devtpl:dev三個任務,然后再執(zhí)行回調里的內容,我們的本地服務器就是要在回調里去定義并且啟動。

在這之前,我們先來了解一下browser-sync:

Browsersync能讓瀏覽器實時、快速響應您的文件更改(html、js、css、sass、less等)并自動刷新頁面。 ——Browsersync中文網(wǎng)

事實上Browsersync可以理解為一個本地服務器,類似于Apache,不同的是,Browsersync只提供很簡單的http功能,它的主要功能,是通過搭建一個本地服務器,并且監(jiān)聽文件的更改,自動刷新瀏覽器,實時地呈現(xiàn)最新內容。

browser-sync的用法:

var browserSync = require('browser-sync').create()

browserSync.init({
  server: {
    baseDir: "./"  // 設置服務器的根目錄
  }
})

其實也很簡單,沒有太多內容,現(xiàn)在我們要將它整合進我們的gulp任務里,搗鼓幾下,我們得到:

gulp.task('dev', ['js:dev', 'sass:dev', 'tpl:dev'], function () {
  browserSync.init({
    server: {
      baseDir: "./dist"  // 設置服務器的根目錄為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任務來處理你,產生最新的文件。

那么,我們現(xiàn)在還需要補充一下js:dev、sass:devtpl:dev這三個任務,與原來的jssasstpl三個任務大同小異,這里籠統(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  // 此配置強制編譯文件不緩存
    }
  }))
  .pipe(gulp.dest('dist'))
  .pipe(reload({stream: true}))
})

這里的reload方法,我們不需要過多了解,只要知道,通過執(zhí)行它就可以刷新瀏覽器。這里的stream用法可以查閱官方文檔,這里不重要所以不細講。

值得留意的是:這里的sass:dev任務,我們并沒有像sass任務一樣配置編譯模式為compressed,也就是不使用壓縮功能,為什么呢?事實上,我們在開發(fā)的時候,并不需要壓縮靜態(tài)資源文件,可以說我們不在意它的體積是大一點還是小一點,我們在意的是樣式是否寫得符合期望,我們在乎的是功能是否實現(xiàn),所以不需要啟用壓縮或者其他的什么優(yōu)化功能,這樣可以減輕編譯的負擔,加快編譯速度。如果你對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  // 此配置強制編譯文件不緩存
    }
  }))
  .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  // 此配置強制編譯文件不緩存
    }
  }))
  .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'])

嗯,完美,到這一步,我們這個自動化構建已經基本完成了,而且還算是完整?,F(xiàn)在,我們開發(fā)的時候,就執(zhí)行gulp dev,打包的時候就執(zhí)行gulp build,是不是很方便?

注意:gulp dev任務啟動以后是一直保持工作狀態(tài)的,也就是它不像gulp build一樣一次性執(zhí)行完,它是keep alive的,所以我們如果要停止這個任務,需要手動按ctrl+c組合鍵,結束這個任務。

五、補充

然而事情還沒完,我們的目標是:裝逼 盡善盡美!

有三個地方其實我們還沒做到位:

  • 對命令進行包裝,盡量簡潔。gulp dev和gulp build其實已經很簡潔了,但事實上這只是因為這個項目很簡單,用到的命令很少,在開發(fā)復雜的項目時,我們通常要輸入復雜的一長串的命令行。gulp dev其實也是gulp --gulpfile gulpfile.js dev的缺省寫法而已,具體情況可以去gulp官網(wǎng)了解。所以我們需要有更簡潔的方式去執(zhí)行這些預先準備好的腳本,就好像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項就是用來預定義腳本的地方,我們可以很方便地把腳本按照上面的形式封裝好,然后執(zhí)行的方式就變成了:

npm run dev  // 執(zhí)行開發(fā)
npm run build  // 執(zhí)行打包

搞定!

我們接著看第二點,刪除dist文件夾,多簡單的事?。∈髽擞益I,刪除,搞定!

……

哪有這么low的事,我們的目標是:懶癌晚期 能不自己做的事情,絕不自己動手。

有一個叫做rimraf的包,可以幫我們做這事,我們需要用到它的cli,所以跟gulp一樣,我們全局安裝它:

npm install rimraf -g

安裝完后,我們再重新修改一下package.json文件中的scripts內容:

{
  "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文件夾是不是先被刪除,然后再重新生成了?

完美。

第三點放到最后才補充,主要是考慮到它并不是必要的,因為有些項目并不需要ftp上傳,一般是提交svn,然后再由后端或者運維去部署,筆者是需要將靜態(tài)資源上傳到cdn服務器進行加速的,所以需要這樣一個任務,在此我們簡單介紹一下。

舉一反三一下,我們再創(chuàng)建一個upload任務:

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',  // 遠程主機ip
    port: 22,  // 端口
    user: 'username',  // 帳號
    pass: 'password',  // 密碼
    remotePath: '/project'  // 上傳路徑,不存在則新建
  }))
  .pipe(gutil.noop())
})

自行安裝一下gulp-ftp和gulp-util兩個包,然后在package.json文件中的scripts補充一個腳本npm run upload來執(zhí)行gulp upload。

筆者通常都是打包之后順便上傳,命令行直接輸入npm run build && npm run upload,回車,然后就可以愉快地去跟旁邊的妹紙聊天了。

六、總結

到這里,我們已經完整搭完了這一套簡易自動化工具,好像講了很多東西,其實總結起來內容非常少:我們只不過分別用三個小任務(sass、js、tpl),組成了build和dev這兩個大任務,僅此而已。熟練的情況下操作起來,整個過程也不過十分鐘!

ps:網(wǎng)絡太差怪我咯?呵呵。

由于時間和篇幅關系,我們只簡單處理了css、js和html,事實上,你還可以在這個基礎上繼續(xù)完善下去,js可以由coffeejs編譯得到,而且還可以繼續(xù)壓縮,甚至可以把全部js文件合并成一個!html也一樣可以繼續(xù)壓縮。而且,你完全可以自己創(chuàng)建一個任務去處理其他諸如圖片、字體等等。

好啦,簡易版就講到這里了。

后話:gulp與webpack都是目前比較流行的編譯打包工具,那么它們之間有什么異同點?怎么去進行選擇?我們下一節(jié)將介紹如何去用webpack搭建一套開發(fā)工具,從中我們可以感受這兩者的差別,敬請留意。

本次演示項目的git地址:gulp_base

【下一篇:進階:構建具備版本管理能力的項目】

(文章有任何謬誤之處,歡迎留言指出)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數(shù),API也非常簡單,學...
    依依玖玥閱讀 3,304評論 7 55
  • gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數(shù),API也非常簡單,學...
    井皮皮閱讀 1,401評論 0 10
  • gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數(shù),API也非常簡單,學...
    小裁縫sun閱讀 1,030評論 0 3
  • gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數(shù),API也非常簡單,學...
    build1024閱讀 585評論 0 0
  • 什么是GULP? GULP 是前端開發(fā)過程中對代碼進行構建的工具,是自動化項目的構建利器;她不僅能 對...
    碧玉含香閱讀 709評論 0 0

友情鏈接更多精彩內容