本文主要介绍前端开发中常用的构建工具Grunt,具体包括Grunt的基本情况、安装、使用和常见插件的安装、配置和使用等内容。

1.1 Grunt简单介绍

Grunt是一套前端自动化构建工具。对于需要反复重复的任务(如压缩、编译、单元测试等),自动化构建工具可以减轻并简化我们的工作。我们只需要在 Gruntfile 文件中正确配置好要处理的任务,任务运行器就会自动帮我们完成大部分工作。

Grunt的优点

❏ Grunt拥有庞大的生态系统,并且一直在增长。
❏ Grunt支持我们自己创作插件并发布。

由于Grunt拥有数量庞大的插件,所以几乎任何的任务都可以利用Grunt来自动完成,你也可以根据自己项目的特点来创作合适的插件发布。

Grunt的工作方式

Grunt为开发者提供了一个工具包,用于创建命令行程序来执行项目构建过程中的重复性任务,比如压缩js代码、编译Sass样式等。Grunt不仅仅能创建简单任务以解决特定工程遇到的特定需求,还能将任务打包为可复用的插件。这些插件可以被发布、分享,使用以及被其他人进行改进。

Grunt的运转依赖于四个核心的组件:分别是Gruntfile、Tasks 、Plugins以及任务配置。

   ① Gruntfile    

Gruntfile指的是在项目根目录下面名为Gruntfile.js的Node模块。该文件使得我们可以加载Grunt插件,创建自定义任务,并根据项目需求对它们进行配置。

Grunt每次运行时的首要任务都是接受该模块发出的指令。

   ② Tasks    

Tasks作为Grunt的基本构建模块,它实际上是由Grunt的registerTask()方法注册的具名函数。

   ③ Plugins    

Plugins是一系列能够用于不同项目的可配置任务的集合。

   ④ 任务配置    

Grunt强调配置优先,任务和插件的功能都可以通过配置文件进行定制,以适应不同工程的需求。这种代码和配置相分离的特性,使开发者能够创造出高复用的插件。

相关参考

现在最新版本     v1.0.2
其它构建工具     gulp、webpack、fis3等

Grunt官网
Grunt官网(中文)
Grunt相关的插件列表

1.2 Grunt的安装

Grunt和相关的插件都通过 npm 安装并管理。

Grunt基于Node.js,安装之前要先安装Node.js。

Node.js的安装

① 打开Node.js官网找到Download选项,选择对应的版本下载。
② 下载之后,根据对应的提示进行安装即可。
③ 安装完成之后,可以通过$ node --version$ npm --version命令查看是否安装成功。

1
2
3
4
5
wendingding:~ wendingding$ node --version
v8.9.3
wendingding:~ wendingding$ npm --version
5.5.1
wendingding:~ wendingding$

安装注意点

❗ ️Grunt依赖于nodejs的v0.8.0及以上版本;
❗ ️奇数版本号的 Node.js 被认为是不稳定的开发版;
❗️ 需确保当前环境中所安装的 npm 已经是最新版本($ npm update -g npm

安装Grunt命令行

注意 在使用Grunt之前,需要先安装Grunt命令行到全局环境中。

安装命令:$ npm install -g grunt-cli

安装完之后,可以通过$ grunt命令来验证Grunt命令行是否安装完成并生效,命令行中的-g表示全局安装。

具体的执行情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
wendingding:~ wendingding$ npm install -g grunt-cli
/usr/local/bin/grunt -> /usr/local/lib/node_modules/grunt-cli/bin/grunt
+ grunt-cli@1.2.0
added 16 packages in 9.289s
wendingding:~ wendingding$ grunt
grunt-cli: The grunt command line interface (v1.2.0)

Fatal error: Unable to find local grunt.

If you're seeing this message, grunt hasn't been installed locally to
your project. For more information about installing and configuring grunt,
please see the Getting Started guide:

http://gruntjs.com/getting-started

Grunt命令行的作用

Grunt命令行用于调用与Gruntfile在同一目录中 Grunt。每次运行Grunt 时,都会根据node提供的require()系统查找本地安装的 Grunt(因此我们可以在项目的任意子目录中运行grunt) ,如果找到一份本地安装的 Grunt,命令行就将其加载,并传递Gruntfile中的配置信息,然后执行指定的任务。

1.3 Grunt的安装和使用

1.3.1 Grunt使用的基本步骤

Grunt使用的基本步骤

①  生成package.json和Gruntfile.js文件
②  命令行安装项目中需要用到的插件
③  编辑Gruntfile文件定义Task并进行配置
④  命令行以grunt task的方式执行任务

1.3.2 Grunt的安装

接下来,我们通过一个完整的Grunt案例来介绍Grunt的常规使用方法。首先创建的对应的项目文件目录,这里命名为Grunt_demo文件夹,然后创建package.json文件Gruntfile.js文件并进行相关配置,安装相应的插件并执行Task。

   ① 创建package.json文件   

创建package.json文件有两种方式,一种是直接创建然后以json格式的字段来进行配置,第二种是通过执行npm install来创建,推荐通过命令行的方式来创建。

✧ 直接创建package.json文件 ✧

1
2
3
4
5
6
wendingding:~ wendingding$ mkdir Grunt_Demo
wendingding:~ wendingding$ cd Grunt_Demo/
wendingding:Grunt_Demo wendingding$ PWD
/Users/文顶顶/Grunt_Demo
wendingding:Grunt_Demo wendingding$ touch package.json
wendingding:Grunt_Demo wendingding$ open package.json

命令行说明

$ mkdir Grunt_Demo 表示创建文件夹
$ cd Grunt_Demo/ 表示进入文件目录
$ PWD 表示查看当前路径
$ touch package.json 表示创建package.json文件
$ open package.json 表示使用记事本打开文件并编辑

1
2
3
4
5
6
7
wendingding:Grunt_Demo wendingding$ open package.json
wendingding:Grunt_Demo wendingding$ cat package.json
{
"name":"Grunt_Demo",
"version":"1.0.0",
"dependencies":{}
}

$ cat package.json 表示查看文件内容

创建好package.json文件后,可以根据需要添加内容字段到文件中。该json文件中最基本字段主要有name、version和dependencies,其中name和version对应的是Grunt项目的名称和版本,而dependencies字段中则列出该项目的依赖。

package.json文件用于被npm存储项目的元数据,以便将此项目发布为npm模块。我们可以在此文件中列出项目依赖的Grunt和Grunt插件,保存在devDependencies(开发依赖)配置段内。

✧ 初始化命令创建package.json文件 ✧

除手动创建外,我们还能够通过命令行来进行初始化操作,会以交互的方式来生成一个包含基本配置信息的package.json文件。

初始化命令:$ npm init

下面列出具体的命令行执行情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
wendingding:Grunt_Demo wendingding$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (grunt_demo)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/文顶顶/Grunt_Demo/package.json:

{
"name": "grunt_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}


Is this ok? (yes) yes
wendingding:Grunt_Demo wendingding$ cat package.json
{
"name": "grunt_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

在执行npm init命令创建基本package.json文件的时候,可以设置名称、版本、依赖等选项,如果不设置直接回车表示以默认(建议)的方式来进行配置。

package.json文件注意点
❐ package.json应当放置于项目的根目录中,并同项目源代码一起管理。
❐ 如果在根目录中运行npm install命令,那么将依据package.json列出的依赖项来自动安装适当版本的依赖。

   ② 创建Gruntfile文件   

Gruntfile文件是Grunt项目中最核心的文件,可以被命名为 Gruntfile.js 或者是Gruntfile.coffee,该文件同package.json文件一起存放在项目的根目录中,主要用来配置或定义任务(task)并加载Grunt插件

标准的grunt项目中必须拥有package.json和Gruntfile这两个文件。

1
2
3
4
5
6
7
wendingding:Grunt_Demo wendingding$ touch Gruntfile.js
wendingding:Grunt_Demo wendingding$ tree -L 2
.
├── Gruntfile.js
└── package.json

0 directories, 2 files

$ tree -L 2 表示以树状图的方式列出当前目录下面的二级文件结构,具体使用可以参考网络编程系列 Mac系统中Tree的使用

   ③ 安装Grunt    

在创建Grunt项目的过程中,我们可以通过$ npm install <module> --save-dev模式的命令来安装Grunt和Grunt插件。该命令在安装的同时,会自动将其添加到package.json文件的devDependencies 配置段中。

接下来我们演示安装Grunt最新版本到项目目录中,并将其添加到devDependencies内。

命令行:$ npm install grunt --save-dev

1
2
3
4
5
6
7
wendingding:Grunt_Demo wendingding$ npm install grunt --save-dev
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN grunt_demo@1.0.0 No description
npm WARN grunt_demo@1.0.0 No repository field.

+ grunt@1.0.2
added 94 packages in 33.833s

命令行执行完毕之后,会发现package.json的配置段中信息发生了变更,在devDependencies配置项中增加了grunt字段和对应的版本信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
wendingding:Grunt_Demo wendingding$ cat package.json
{
"name": "grunt_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"grunt": "^1.0.2"
}
}

项目的根目录中增加了node_modules文件中,该目录列出了必要的依赖文件,下面给出文件结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
wendingding:Grunt_Demo wendingding$ tree -L 2
.
├── Gruntfile.js
├── node_modules
│ ├── abbrev
│ ├── ansi-regex
│ ├── ansi-styles
│ ├── argparse
│ ├── array-find-index
│ ├── async
│ ├── balanced-match
│ ├── brace-expansion
│ ├── builtin-modules
│ ├── camelcase
│ ├── camelcase-keys
│ ├── chalk
│ ├── coffeescript
│ ├── colors
│ ├── concat-map
│ ├── currently-unhandled
│ ├── dateformat
│ ├── decamelize
│ ├── error-ex
│ ├── escape-string-regexp
│ ├── esprima
│ ├── eventemitter2
│ ├── exit
│ ├── find-up
│ ├── findup-sync
│ ├── fs.realpath
│ ├── get-stdin
│ ├── getobject
│ ├── glob
│ ├── graceful-fs
│ ├── grunt
│ ├── grunt-known-options
│ ├── grunt-legacy-log
│ ├── grunt-legacy-log-utils
│ ├── grunt-legacy-util
│ ├── has-ansi
│ ├── hooker
│ ├── hosted-git-info
│ ├── iconv-lite
│ ├── indent-string
│ ├── inflight
│ ├── inherits
│ ├── is-arrayish
│ ├── is-builtin-module
│ ├── is-finite
│ ├── is-utf8
│ ├── isexe
│ ├── js-yaml
│ ├── load-json-file
│ ├── lodash
│ ├── loud-rejection
│ ├── map-obj
│ ├── meow
│ ├── minimatch
│ ├── minimist
│ ├── nopt
│ ├── normalize-package-data
│ ├── number-is-nan
│ ├── object-assign
│ ├── once
│ ├── parse-json
│ ├── path-exists
│ ├── path-is-absolute
│ ├── path-type
│ ├── pify
│ ├── pinkie
│ ├── pinkie-promise
│ ├── read-pkg
│ ├── read-pkg-up
│ ├── redent
│ ├── repeating
│ ├── resolve
│ ├── rimraf
│ ├── safer-buffer
│ ├── semver
│ ├── signal-exit
│ ├── spdx-correct
│ ├── spdx-exceptions
│ ├── spdx-expression-parse
│ ├── spdx-license-ids
│ ├── sprintf-js
│ ├── strip-ansi
│ ├── strip-bom
│ ├── strip-indent
│ ├── supports-color
│ ├── trim-newlines
│ ├── underscore.string
│ ├── validate-npm-package-license
│ ├── which
│ └── wrappy
├── package-lock.json
└── package.json

91 directories, 3 files

至此,Grunt项目的基本配置以及Grunt的安装已经完成,在开发中使用Grunt主要是用Grunt相关的一些插件来实现特定的功能。Grunt的生态中提供了非常丰富的插件,我们可以直接在官方搜索查看,接下来给大家介绍几个在前端项目构建中常用到的插件。

1.3.3 Grunt插件的安装和使用

文件合并插件concat的安装和使用

concat插件的地址:https://github.com/gruntjs/grunt-contrib-concat

concat插件安装命令:$ npm install grunt-contrib-concat --save-dev

--save-dev参数表示插件安装完成后,记录相关信息到package.json文件中的devDependencies配置项。

下面列出具体的执行情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
wendingding:Grunt_Demo wendingding$ npm install grunt-contrib-concat --save-dev
npm WARN grunt_demo@1.0.0 No description
npm WARN grunt_demo@1.0.0 No repository field.

+ grunt-contrib-concat@1.0.1
added 2 packages in 3.165s
wendingding:Grunt_Demo wendingding$ cat package.json
{
"name": "grunt_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"grunt": "^1.0.2",
"grunt-contrib-concat": "^1.0.1"
}
}

插件安装完成后,在项目的node_modules文件目录会新增加grunt-contrib-concat模块。接下来我们通过编辑Gruntfile文件来定义和配置Task。

在项目的根目录中创建src文件夹,在该文件夹下面创建两个示例的js文件,分别为demo_one.js和demo_two.js

demo_one.js文件的内容

1
2
3
4
5
//声明demoOne函数并执行
function demoOne() {
console.log("demoOne.js文件中的内容");
}
demoOne();

demo_two.js文件的内容

1
2
3
4
5
//声明demoTwo函数并执行
function demoTwo() {
console.log("demoTwo.js文件中的内容");
}
demoTwo();

编辑Gruntfile文件定义和配置Task

接下来我们需要编辑Gruntfile文件,在该文件中告诉grunt具体的任务(Task)是什么,以及这些任务(Task)应该如何执行,下面给出示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//包装函数,规定所有的代码都需要写在该函数内部
module.exports = function (grunt) {

//项目配置信息
grunt.initConfig({
//表示从package文件中加载json数据,并保存到pkg属性中
pkg:grunt.file.readJSON("package.json"),
//concat任务的配置信息
"concat":{
dist: {
//把src目录下面的demo_one和demo_two文件合并成demo.js文件保存到dist目录
src: ['src/demo_one.js', 'src/demo_two.js'],
dest: 'dist/demo.js',
}
}

})

//加载包含concat任务的插件
grunt.loadNpmTasks("grunt-contrib-concat");

//设置默认执行的任务列表
grunt.registerTask("default",["concat"]);
};

代码说明

上面的示例代码主要由三部分组成:配置任务相关代码 + 加载插件相关代码 + 注册任务相关代码,所有的代码都需要写在module.exports这个包装函数内部,grunt作为包装函数的参数传递。

这里代码中的pkg部分并非必要,loadNpmTasks方法用于从node_modules中加载对应的插件,registerTask方法表示把concat这个任务加入到默认的任务队列中(该行代码并非必需),如果不写该行代码则可以直接以$ grunt concat的方式执行合并任务。当然也可以通过registerTask方法来给Task注册个别名,然后通过$ grunt 别名指令来执行该Task。

当前的目录结构如下(注:省略node_modules目录细节)

1
2
3
4
5
6
7
8
9
wendingding:Grunt_Demo wendingding$ tree -L 1
.
├── Gruntfile.js
├── node_modules
├── package-lock.json
├── package.json
└── src
├── demo_one.js
└── demo_two.js

不同插件的使用方式可能也不尽相同,插件的具体用法请参考对应的文档说明。通过编辑Gruntfile文件指定任务的配置项、加载插件并注册任务后,就可以通过命令行来执行Task了。

执行Task

执行Task的命令行:$ grunt 或者是$ grunt default 或者是$ grunt concat
命令行输出结果

1
2
3
4
wendingding:Grunt_Demo wendingding$ grunt default
Running "concat:dist" (concat) task

Done.

Task执行结束后,src目录下面的demo_one.js和demo_two.js两个文件会被合并成demo.js文件并保存到dist目录下,如果指定的目录不存在那么将会直接创建。

压缩插件uglify和cssmin的安装和使用

创建新的文件目录Grunt_Test来演示javaScript的压缩插件uglify以及CSS的压缩插件cssmin的使用,创建好文件目录之后,同样通过$ npm init初始化命令来生成基础的package.json文件。

先安装grunt到本地的项目中,具体命令如下:

$ npm install grunt --save-dev

然后下载需要用到的对应插件到本地的项目中,具体命令如下 :

$ npm install grunt-contrib-uglify --save-dev 表示安装uglify插件

$ npm install grunt-contrib-cssmin --save-dev 表示cssmin插件

上面的命令行执行完毕后,grunt就会把两个压缩插件下载到node_modules文件目录下,可以通过在该目录下查找grunt-contrib-uglify和grunt-contrib-cssmi文件进行验证。

--save--dev参数会把下载记录更新到package.json文件中的devDependencies字段。

1
2
3
4
5
"devDependencies": {
"grunt": "^1.0.2",
"grunt-contrib-cssmin": "^2.2.1",
"grunt-contrib-uglify": "^3.3.0"
}

为了演示压缩插件的具体使用,下面我们在项目根目录下创建index.js文件,并新建style文件夹,并在该目录下创建index.css文件,具体的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
.
├── node_modules
│ ├── ...(省略)
│ ├── grunt-contrib-cssmin
│ ├── grunt-contrib-uglify
├── package-lock.json
├── package.json
└── src
├── index.js
└── style
└── index.css

index.js文件内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Created by wendingding on 18/5/19.
*/

var a = 123;
var b = "文顶顶";
function sum(a,b) {
return a + b;
}

(function (c) {
console.log("______" + c);
})(window);

index.css文件内容为

1
2
3
4
5
6
7
8
body{
background: red;
}
*{
margin: 0;
padding: 0;
list-style: none;
}

接下来我们创建并编辑Gruntfile文件,通过特定的代码定义和配置Task。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//包装函数
module.exports = function (grunt) {

var app = {
src:"src/",
dist:"dist/"
};

//(1) 项目配置信息
//说明:initConfig方法等价于grunt.config.init()方法;
grunt.initConfig({
//定义js文件压缩Task: 表示把src目录下面的index.js文件压缩到dist目录中的index.min.js
"uglify":{
target:{
src:app.src + "index.js",
dest:app.dist + "index.min.js"
}
},
//定义css文件压缩Task: 表示把src/style目录中的index.css文件压缩到dist目录中的index.min.css
"cssmin":{
target:{
src:app.src + "style/index.css",
dest:app.dist + "index.min.css"
}
}
});

//(2) 加载对应的插件
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-cssmin");

//(3) 注册任务
//002 注册任务的第一种方式
//① 这种方式可以不写任何注册任务相关的代码
//② 我们可以通过$ grunt uglify和$ grunt cssmin命令来分别执行这两个Task
//③ 支持以$ grunt uglify cssmin的方式来依次执行多个Task

//002 注册任务的第二种方式
//① 这种方式相当于给每个任务都起一个Task名称,通过$ grunt task名称的方式执行
//② 执行命令 $ grunt uglifyTask 表示执行js文件的压缩操作
//③ 执行命令 $ grunt cssminTask 表示执行css文件的压缩操作
//④ 执行命令 $ grunt cssminTask uglifyTask 表示先执行css文件的压缩,再执行js文件的压缩
//grunt.registerTask("uglifyTask","uglify");
//grunt.registerTask("cssminTask","cssmin");

//003 注册任务的第三种方式
// ① 这种方式把多个任务添加到default任务队列中,执行$ grunt default的时候,所有的Task依次执行
// ② 执行命令为 $ grunt default 或者是$ grunt 因为default可以被省略
// grunt.registerTask("default",["uglify","cssmin"]);
};

根据任务注册的不同方式来执行Task,下面分别给出三种方式的执行命令

方式(1)先执行$ grunt cssmin再执行 $ grunt uglify,或者通过$ grunt cssmin uglify命令来依次执行多个任务。

方式(2)先执行$ grunt cssminTask再执行 $ grunt uglifyTask,或者通过$ grunt cssminTask uglifyTask命令来依次执行多个任务。

方式(3)通过$ grunt或者是$ grunt default命令来依次执行多个任务。

1
2
3
4
5
6
7
8
wendingding:Grunt_Test wendingding$ grunt default
Running "uglify:target" (uglify) task
>> 1 file created 174 B → 93 B

Running "cssmin:target" (cssmin) task
>> 1 file created. 89 B → 57 B

Done.

当两个任务执行完毕后,项目中会创建dist目录,该目录中新增两个文件分别对应压缩版的js文件和压缩版的css文件,新的目录结构如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── node_modules
│ ├── ...(省略)
│ ├── grunt-contrib-cssmin
│ ├── grunt-contrib-uglify
├── package-lock.json
├── package.json
├── dist
│ ├── index.min.js
│ └── index.min.css
└── src
├── index.js
└── style
└── index.css

上文列出了代码合并插件concat和压缩插件uglify|uglify的安装和基本使用过程,grunt生态系统拥有数量庞大的高质量插件群体,无法一一介绍,可以到Grunt相关的插件列表页面-中文Grunt相关的插件列表页面-官网自行查看。

Grunt插件使用总结

❏ 创建package.json文件(简单配置)和Gruntfile文件($ npm init)
❏ 通过命令行把Grunt下载和安装到本地项目中($ npm install grunt --save-dev)
❏ 通过命令行把Grunt插件下载和安装到本地项目中($ npm install grunt-contrib-xxx)
❏ 在Gruntfile文件中对Grunt插件的Task进行配置(grunt.initConfig)
❏ 在Gruntfile文件中通过代码来加载对应的插件(grunt.loadNpmTasks)
❏ 在Gruntfile文件中通过代码来注册任务(grunt.registerTask)
❏ 在命令行中通过grunt + 任务名的方式来执行Task或加入到default队列以grunt命令执行。