前端工程化系列[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的更多用法建议查看使用文档

相关文章
相关标签/搜索