前端工程化系列[04]-Grunt構建工具的使用進階

前端工程化系列[02]-Grunt構建工具的基本使用前端工程化系列[03]-Grunt構建工具的運起色制這兩篇文章中,咱們對Grunt以及Grunt插件的使用已經有了初步的認識,並探討了Grunt的主要組件以及它的運起色制,這篇文章是Grunt使用的進階教程,主要輸出如下內容:css

❏  Grunt項目的自定義任務
❏  Grunt任務的描述和依賴
❏  Grunt多目標任務和選項
❏  Grunt項目任務模板配置
❏  Grunt自動化構建和監聽前端

3.1 Grunt自定義任務

在使用Grunt的時候,能夠先到Grunt官網的插件列表搜索是否有適合本身項目的Grunt插件,若是有那麼建議直接使用,若是沒有那麼開發者能夠嘗試自定義任務或者是本身建立對應的插件。Grunt的插件其實就是一些封裝好的任務(Task),沒有什麼稀奇的,Grunt支持自定義任務,並且方式很是簡單。node

若是咱們須要定義一個任務,向控制檯裏輸出字符串信息,那麼在package.json文件、Gruntfile文件已經建立且grunt本地依賴已安裝的前提下,以下編輯Gruntfile文件便可:git

//包裝函數
module.exports = function (grunt) {
 
//(1)自定義任務(一)
//向控制檯輸出:hello 文頂頂
//第一個參數:任務的名稱(Task)
//第二個參數:具體的任務內容
grunt.registerTask("hello",function () {
grunt.log.writeln("hello 文頂頂");
});
 
//(2)自定義任務(二)
grunt.registerTask("football",function () {
grunt.log.writeln("皇家馬德里: how are you!");
grunt.log.writeln("尤文圖斯: how old are you!");
});
};

終端輸入命令執行任務,能夠單個執行,也能夠一塊兒執行,下面給出具體執行狀況github

wendingding:02-Grunt_Test wendingding$ grunt hello
Running "hello" task
hello 文頂頂
 
Done.
wendingding:02-Grunt_Test wendingding$ grunt football
Running "football" task
皇家馬德里: how are you!
尤文圖斯: how old are you!
 
Done.
wendingding:02-Grunt_Test wendingding$ grunt hello football
Running "hello" task
hello 文頂頂
 
Running "football" task
皇家馬德里: how are you!
尤文圖斯: how old are you!
Done.

經過上面的代碼咱們能夠看到,自定義任務很是簡單,只須要調用grunt對象的registerTask方法便可,其中第一個參數是Task的名稱,第二個參數是回調函數用來存放具體的任務(好比這裏是打印輸出)。npm

在自定義任務中,咱們用到了grunt.log.writeln函數,這是Grunt提供的衆多內置方法之一,做用是向控制檯輸出消息並換行。同類型的方法還有grunt.log.error()、grunt.log.subhead()等方法,你們能夠到 官網API文檔自行查看。

Grunt項目在具體使用的時候,一般是自定義Task + Grunt插件相結合的形式,咱們來看下面這段代碼:json

//包裝函數
module.exports = function (grunt) {
 
//(1)自定義任務(一) 任務名稱 hello
grunt.registerTask("hello",function () {
grunt.log.writeln("hello 文頂頂");
});
 
//(2)自定義任務(二) 任務名稱 football
grunt.registerTask("football",function () {
grunt.log.writeln("皇家馬德里: how are you!");
grunt.log.writeln("尤文圖斯: how old are you!");
});
 
 
//(2) 插件的處理
//使用步驟:
//[1] 先把對應的插件下載和安裝到本地的項目中 $ npm install grunt-contrib-concat --save-dev
//[2] 對插件(任務)進行配置 grunt.initConfig
//[3] 加載對應的插件 loadNpmTasks
//[4] 註冊任務 grunt.registerTask
//[5] 經過grunt命令執行任務
//配置插件相關信息
grunt.initConfig({
"concat":{
"dist":{
"src":["src/demo_one.js","src/demo_two.js","src/demo_three.js"],
"dest":"dist/index.js"
}
}
});
 
//加載插件
grunt.loadNpmTasks("grunt-contrib-concat");
 
//註冊任務(一):把hello \ football \ concat 這三個Task註冊爲default的Task
//當執行$ grunt 或者是$ grunt default的時候,會順序執行者三個任務!
grunt.registerTask("default",["hello","football","concat"]);
//註冊任務(二)
grunt.registerTask("customTask",["hello","football"]);
};


3.2 任務描述和依賴

對於上面的Gruntfile文件,若是在終端輸入$ grunt或者$ grunt default 命令則依次執行hello football和concat三個任務,輸入$ grunt customTask則一次執行hello football 自定義任務。前端工程化

 

設置任務描述api

隨着項目複雜性的增長,Grunt任務也會愈來愈多,而任務(Task)的可用性、用途以及調用方法可能會變得難以追蹤。所幸,咱們能夠經過給任務設定相應的描述信息來解決這些問題。數組

要給任務設置描述信息很是簡單,只須要在調用registerTask方法的時候多傳遞一個參數便可(做爲第二個參數傳遞),咱們能夠把一個具體的字符串描述信息做爲函數的參數傳遞。

這裏,咱們修改上面示例代碼中football任務部分的代碼,並任務設置描述信息。

grunt.registerTask("football","17-18賽季 歐冠八分之一決賽抽籤場景",function () {
grunt.log.writeln("皇家馬德里: how are you!");
grunt.log.writeln("尤文圖斯: how old are you!");
});

此時,在終端中輸入$ grunt --help命令就可以看到當前Grunt項目中可用的Task,以及相應的描述信息了,關鍵信息以下。

Available tasks
hello Custom task.
football 17-18賽季 歐冠八分之一決賽抽籤場景
concat Concatenate files. *
default Alias for "hello", "football", "concat" tasks.
customTask Alias for "hello", "football" tasks.

任務依賴

在複雜的Grunt工做流程中,不少任務之間每每存在依賴關係,好比js代碼的語法檢查和壓縮這兩個任務,壓縮任務須要依賴於語法檢查任務,它們在執行的時候存在必定的前後關係,這種狀況咱們稱之爲任務依賴。

咱們能夠在註冊任務的時候,刻意指定這種依賴關係,他們更多的是以一種特定的前後順序執行。若是是自定義任務,也能夠經過grunt.task.requires()方法來設定這種任務間的依賴關係。

module.exports = function (grunt) {
//註冊兩個自定義任務
/*
* 第一個參數:Task的名稱
* 第二個參數:任務的描述信息
* */
grunt.registerTask("hi","描述信息:這是一個打招呼的任務",function () {
grunt.log.ok("hi 文頂頂");
});
 
grunt.registerTask("hello","任務的描述次信息:這是一個簡單問候任務",function () {
//設置任務依賴:代表當前的任務在執行的時候須要依賴於另一個任務
//必須先執行hi這個任務,才能執行hello這個任務
grunt.task.requires("hi");
console.log("Nice to meet you!");
});
};

上面的代碼中定義了hi和hello兩個任務,其中hello這個Task須要依賴於hi的執行,若是直接執行hello,那麼會打印任務依賴的提示信息,具體的執行狀況以下。

wendingding:05-Grunt項目任務的描述和依賴 wendingding$ grunt hello
Running "hello" task
Warning: Required task "hi" must be run first. Use --force to continue.
 
Aborted due to warnings.
wendingding:05-Grunt項目任務的描述和依賴 wendingding$ grunt hi hello
Running "hi" task
>> hi 文頂頂
 
Running "hello" task
Nice to meet you!
Done.

3.3 Grunt多目標任務和Options選項

理解多目標Task

Grunt中的多目標任務(multi-task)是相對於基本任務而言的,多目標任務幾乎是Grunt中最複雜的概念。它的使用方式很是靈活,其設計的目的是能夠在當個項目中支持多個Targets目標[能夠認爲是多種配置]。當任務在執行的時候,能夠一次性執行所有的Target也能夠指定某一特定的Target執行。

module.exports = function (grunt) {
//(1) 配置Task,給Task設置多個Target
grunt.config("hello",
{
    "targetA":{
        "des":"Nice to meet you!"
                 },
    "targetB":{
        "des":"how are you?"
                },
});

 
//(2) 自定義任務 任務的名稱爲hello
//第一個參數:Task名稱
//第二個參數:任務的描述信息
//第三個參數:具體要執行的任務
grunt.registerMultiTask("hello","描述信息:打招呼",function () {
    grunt.log.ok("hello 文頂頂");
    grunt.log.writeln("this.target:",this.target);
    grunt.log.writeln("this.data:",this.data);
});
};                                    

代碼說明

經過觀察能夠發現,咱們經過grunt.registerMultiTask方法建立了支持多任務(Target)操做的自定義任務hello,主要任務就是輸出「hello 文頂頂」消息以及打印當前的target和data值。而後經過grunt.config方法來給hello這個Task設定了兩個Target,分別是targetA和targetB。

在上面的代碼中,咱們引用了this.target和this.data這兩個屬性,回調函數中的this指向的是當前正在運行的目標對象。執行targetA這個選項的時候,打印的this對象以下:

{ 
nameArgs: 'hello:targetA',
name: 'hello',
args: [],
flags: {},
async: [Function],
errorCount: [Getter],
requires: [Function: bound ],
requiresConfig: [Function],
options: [Function],
target: 'targetA',
data: { des: 'Nice to meet you!' },
files: [],
filesSrc: [Getter] 
}

 

目前爲止,咱們一直在談論Task(任務)和Target(目標),你們可能懵逼了,不由要問它們之間究竟是什麼關係?

私覺得能夠簡單的類比一下,假設如今有一個任務就是中午吃大餐,而具體吃什麼大餐,能夠靈活安排多個方案進行選擇,好比方案A吃西餐,方案B吃中餐,方案C吃日本料理。等咱們真正到了餐館要開吃的時候,能夠選擇方案A吃西餐或者是方案B吃中餐,甚至中餐、西餐和日本料理全端上桌也何嘗不可。

Task指的是整個任務,在這個例子中就是要吃大餐,Target指的是任務中的某一種可行方案,也就是方案A、方案B和方案C,吃大餐這個Task中咱們配置了三個Target。定義任務的目的是爲了執行,在執行Task的時候,咱們能夠選擇執行某個或某幾個指定的Target(目標),這樣的處理方式無疑更強大並且操做起來更加的靈活。

 

多目標任務的執行

運行多目標Task的時候,有多種方式選擇。

① 讓Task按照指定的target運行。$ grunt TaskName:targetName

② 讓Task把全部的target都運行一次。$ grunt TaskName

下面列出示例代碼的具體執行狀況

wendingding:05-Grunt項目任務的描述和依賴 wendingding$ grunt hello
Running "hello:targetA" (hello) task
>> hello 文頂頂
this.target: targetA
this.data: { des: 'Nice to meet you!' }
 
Running "hello:targetB" (hello) task
>> hello 文頂頂
this.target: targetB
this.data: { des: 'how are you?' }
 
Done.
wendingding:05-Grunt項目任務的描述和依賴 wendingding$ grunt hello:targetA
Running "hello:targetA" (hello) task
>> hello 文頂頂
this.target: targetA
this.data: { des: 'Nice to meet you!' }
 
Done.
wendingding:05-Grunt項目任務的描述和依賴 wendingding$ grunt hello:targetB
Running "hello:targetB" (hello) task
>> hello 文頂頂
this.target: targetB
this.data: { des: 'how are you?' }
 
Done.
若是在Gruntfile文件中,調用了 grunt.registerTask方法來註冊自定義任務,那麼能夠經過 TaskName:targetName的來方式直接指定任務的Target
//註冊任務 [給hello起一個別名]
grunt.registerTask("helloTargetA",["hello:targetA"]);

在終端中,輸入$ grunt helloTargetA 命令將會執行hello這個Task中的targetA選項。

 

多目標任務的Options選項

在對多目標的任務進行配置的時候,任何存儲在options選項下面的數據都會被特殊的處理。

下面列出一份Gruntfile文件中的核心代碼,並以多種方式執行,經過這份代碼可以幫助咱們理解多目標任務的Options選項配置。

//包裝函數
module.exports = function (grunt) {
 
//(1) 配置Task相關信息
/*
* 第一個參數:Task的名稱
* 第二個參數:任務的描述信息
* */
grunt.initConfig({
"hi": {
/*對整個任務中全部target的配置項 全局配置*/
options:{
    "outPut":"array"
    },
targetA:{
    arrM:["targetA_1","targetA_2","targetA_3"]
},
targetB:{
    options:{
        "outPut":"json"
    },
    arrM:["targetB_1","targetB_2","targetB_3"]
    },
targetC:{
    arrM:["targetC_1","targetC_2","targetC_3"]
    }
    }
});
 
//(2) 自定義任務 Task名稱爲hi
//第一個參數:Task名稱
//第二個參數:任務的描述信息
//第三個參數:具體要執行的任務
grunt.registerMultiTask("hi","描述次信息:這是一個打招呼的任務",function () {
    console.log("任務當前執行的target: "+this.target);
    console.log("任務當前執行的target對應的數據: \n");
 
var objT = this.options();
if (objT.outPut === "array")
{
    console.log("輸出數組:\n");
    console.log(this.data.arrM);
}else if (objT.outPut === "json")
{
    console.log("輸出JSON數據:\n");
    console.log(JSON.stringify(this.data.arrM));
}
});
 
//(1) 相關的概念 Task(任務-hi) | target(目標)
//(2) 任務的配置:任務中能夠配置一個或者是多個目標 調用config
//(3) 複合任務的執行(多任務-多target)
// 001 grunt TaskName 把當前Task下面全部的目標操做都執行一遍
// 002 grunt TaskName:targetName 執行當前Task下面的某一個指定的目標
    grunt.registerTask("default",["hi"]);
};

具體的執行狀況

wendingding:06-Grunt項目多任務和options wendingding$ grunt
Running "hi:targetA" (hi) task
任務當前執行的target: targetA
任務當前執行的target對應的數據:
 
輸出數組:
[ 'targetA_1', 'targetA_2', 'targetA_3' ]
 
Running "hi:targetB" (hi) task
任務當前執行的target: targetB
任務當前執行的target對應的數據:
 
輸出JSON數據:
["targetB_1","targetB_2","targetB_3"]
 
Running "hi:targetC" (hi) task
任務當前執行的target: targetC
任務當前執行的target對應的數據:
 
輸出數組:
[ 'targetC_1', 'targetC_2', 'targetC_3' ]
 
Done

代碼說明

上面的代碼中定義了一個多目標任務,Task的名稱爲hi,該Task有三個target目標選項,分別是targetA、targetB和targetC。在任務配置相關代碼中,全局的options配置項中outPut屬性對應的值爲array,表示具體的目標任務在執行的時候以數組的形式輸出。

咱們看到在targetB目標中重寫了options選項中的outPut屬性爲json,當終端執行$ grunt命令的時候,會依次執行全部三個target目標選項,而targetA和targetC以數組格式來輸出內容,targetB則以json格式來輸出內容。

Grunt多目標任務以及選項使得咱們能夠針對不一樣的應用環境,以不一樣的方式來運行同一個Task。能夠利用這一點,咱們徹底可以定義Task爲不一樣的構建環境建立不一樣的輸出目標。

說明 ✧  this.options()方法用於獲取當前正在執行的目標Task的options配置選項

3.4 Grunt項目任務配置模板

Grunt項目中配置模板的簡單使用

在Grunt項目中,咱們可使用<% %>分隔符的方式來指定模板,當Task讀取本身配置信息的時候模板的具體內容會自動擴展,且支持以遞歸的方式展開。

在經過<%= ... %>在向模板綁定數據的時候,咱們能夠直接傳遞配置對象中的屬性或調用grunt提供的方法,模板中屬性的上下文就是當前的配置對象。

下面,咱們經過Gruntfile文件中的一段核心代碼來展示配置模板的使用狀況。

module.exports = function (grunt) {

//(1) 建立並設置grunt的配置對象
//配置對象:該對象將做爲參數傳遞給grunt.config.init方法
var configObj = {
concat: {
target: {
    //src:["src/demo1.js","src/demo2.js"]
    src: ['<%= srcPath %>demo1.js', '<%= srcPath %>demo2.js'],
    //dest:["dist/2018_05_21_index.js"]
    dest: '<%= targetPath %>',
},
},
srcPath:"src/",
destPath:"dist/",
targetPath:"<%= destPath %><%= grunt.template.today('yyyy_mm_dd_') %>index.js"
};
 
//(2) 調用init方法對任務(Task)進行配置
// grunt.config.init 方法 === grunt.initConfig方法
grunt.config.init(configObj);
 
//(3) 加載concat插件
grunt.loadNpmTasks("grunt-contrib-concat");
 
//(4) 註冊Task
grunt.registerTask("default",["concat"]);
};

上面這段代碼對concat插件代碼合併Task進行了配置,使用到了模板技術。該任務把src目錄下的demo1和demo2兩個js文件合併到dist目錄下並命名爲2018_05_21_index.js文件。

 

Grunt項目中導入外部的數據

在向模板綁定數據的時候,常見的作法還會導入外部的數據,並把導入的數據設置爲配置對象的指定屬性值。好比在開發中經常須要用到當前Grunt項目的元信息,包括名稱、版本等,這些數據常經過調用grunt.file.readJSON方法加載package.json文件的方式獲取。下面給出代碼示例:

//包裝函數
module.exports = function (grunt) {
 
//設置(demoTask和concat)Task的配置信息
grunt.config.init({
//從package.json文件中讀取項目的元(基本)信息
pkg:grunt.file.readJSON("package.json"),
//demoTask的配置信息
demoTask :{
banner:"<%=pkg.name%> -- <%=pkg.version%>"
},
//concat的配置信息
concat:{
options:{
stripBanners:true,
banner:'/*項目名稱:<%=pkg.name%> 項目版本:<%=pkg.version%> 項目的做者:<%=pkg.author%> 更新時間:<%=grunt.template.today("yyyy-mm-dd")%>*/\n'
},
target:{
src:["src/demo1.js","src/demo2.js"],
dest:'dist/index.js'
}
}
});
 
//自定義Task 任務的名稱爲demoTask
grunt.registerMultiTask("demoTask",function () {
console.log("執行demo任務");
//表示調用config方法來讀取demoTask裏面的banner屬性並輸出
console.log(grunt.config("demoTask.banner"));
});
 
//從node_modules目錄中加載concat插件
//注意:須要先把插件下載到本地 npm install grunt-contrib-concat --save-dev
grunt.loadNpmTasks("grunt-contrib-concat");
 
//註冊任務
grunt.registerTask("default",["demoTask","concat"]);
};

若是在終端輸入$ grunt命令執行,那麼demoTask任務將會輸出grunt_demo -- 1.0.0打印消息,而concat任務則把兩個js文件合併到dist目錄下面的index.js文件並添加註釋信息。

wendingding$ grunt
Running "demoTask:banner" (demoTask) task
執行demo任務
grunt_demo -- 1.0.0
 
Running "concat:target" (concat) task
 
Done.
wendingding:07-Grunt項目模板配置 wendingding$ cat dist/index.js
/*項目名稱:grunt_demo 項目版本:1.0.0 項目的做者:文頂頂 更新時間:2018-05-21*/
console.log("demo1");
console.log("demo2");
 
說明  grunt.file.readJSON方法用於加載JSON數據,grunt.file.readYAML方法用於加載 YAML數據。

3.5 Grunt自動化構建和監聽

到這裏,基本上就能夠說已經熟練掌握Grunt了。上文咱們在進行代碼演示的時候,不管是自定義任務仍是Grunt插件使用的講解都是片斷性的,支離破碎的,Grunt做爲一款自動化構建工具,自動化這三個字到如今尚未體現出來。

顧名思義,自動化構建的意思就是可以監聽項目中指定的文件,當這些文件發生改變後自動的來執行某些特定的任務。 不然的話,每次修改文件後,都須要咱們在終端裏面輸入對應的命令來從新執行,這頂多能算半自動化是遠遠不夠的。

下面給出一份更全面些的Gruntfile文件,該文件中使用了幾款經常使用的Grunt插件(uglify、cssmin、concat等)來搭建自動化構建項目的工做流。點擊獲取演示代碼

//包裝函數
module.exports = function (grunt) {
// 項目配置信息
grunt.config.init({
pkg:grunt.file.readJSON("package.json"),
//代碼合併
concat:{
options:{
stripBanners:true,
banner:'/*項目名稱:<%=pkg.name%> 項目版本:<%=pkg.version%> 項目的做者:<%=pkg.author%>'
+' 更新時間:<%=grunt.template.today("yyyy-mm-dd")%>*/\n'
},
target:{
src:["src/demo1.js","src/demo2.js"],
dest:'dist/index.js'
}
},
//js代碼壓縮
uglify:{
target:{
src:"dist/index.js",
dest:"dist/index.min.js"
}
},
//css代碼壓縮
cssmin:{
target:{
src:"src/index.css",
dest:"dist/index.min.css"
}
},
//js語法檢查
jshint:{
target:['Gruntfile.js',"dist/index.js"],
options:{
jshintrc:".jshintrc"
}
},
//監聽 自動構建
watch:{
target:{
files:["src/*.js","src/*.css"],
//只要指定路徑的文件(js和css)發生了變化,就自動執行tasks中列出的任務
tasks:["concat","jshint","uglify","cssmin"]
}
}
});
 
//經過命令行安裝插件(省略...)
//從node_modules路徑加載插件
grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-cssmin");
grunt.loadNpmTasks("grunt-contrib-jshint");
grunt.loadNpmTasks("grunt-contrib-watch");
 
//註冊任務:在執行$ grunt命令的時候依次執行代碼的合併|檢查|壓縮等任務並開啓監聽
grunt.registerTask("default",["concat","jshint","uglify","cssmin","watch"])
};
當在終端輸入$ grunt命令的時候,grunt會執行如下任務
①  合併src/demo1.js和src/demo2.js文件並命名爲index.js保存到dist目錄
②  按照既定的規則對Gruntfile.js和index.js文件來進行語法檢查
③  壓縮index.js文件並命名爲index.min.js保存在dist目錄
④  壓縮src/index.css文件並保存到dist/index.min.css
⑤  開啓監聽,若是src目錄下面的js文件或css文件被更改則從新構建

關於監聽插件grunt-contrib-watch的更多用法建議查看使用文檔

相關文章
相關標籤/搜索