玩转Grunt(二): Gruntfile 详解

Gruntfile.js是Grunt工具的项目配置文件,它主要用来定义需要运行的任务及配置信息、加载这些任务使用的插件和注册他们的运行顺序。要想读懂这个文件,首先需要了解Grunt 的运行机制以及配置。

1. How Grunt works

Grunt是基于nodejs平台开发和运行的,那么讨论grunt一切都离不开nodejs的模块机制。在Grunt中核心模块即是Grunt,它提供了grunt用到的的所有的公共方法和属性。当我们在命令行运行Grunt命令时,它会以node的require方式加载本地安装的grunt模块(包括内部api),并应用Gruntfile中的配置项,最后执行指定的任务或者缺省的任务列表。

以下几段代码也证实了这一点:

node_modules/grunt/lib/grunt/cli.js

cli.js
1
2
3
4
5
6
7
8
9
var grunt = require('../grunt');

// This is only executed when run via command line.
var cli = module.exports = function(options, done) {
//process cli options
...
// Run tasks.
grunt.tasks(cli.tasks, cli.options, done);
};

node_modules/grunt/lib/grunt.js

grunt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// The module to be exported.
var grunt = module.exports = {};

// Expose internal grunt libs.
function gRequire(name) {
return grunt[name] = require('./grunt/' + name);
}
var util = gRequire('util');
gRequire('file');
var option = gRequire('option');
var config = gRequire('config');
var task = gRequire('task');
gRequire('cli');
..

node_modules/grunt/lib/grunt/tasks.js

grunt/tasks.js
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
var grunt = require('../grunt');

// Get any local Gruntfile or tasks that might exist. Use --gruntfile override
// if specified, otherwise search the current directory or any parent.
if (gruntfile && grunt.file.exists(gruntfile)) {
...
// Load local tasks, if the file exists.
loadTask(gruntfile);
...
}
...
// Load tasks and handlers from a given tasks file.
var loadTaskStack = [];
function loadTask(filepath) {
...
var fn;
try {
// Load taskfile.
fn = require(path.resolve(filepath));
if (typeof fn === 'function') {
fn.call(grunt, grunt);
}
...
}
}

2. Grunt 配置

看到上面的fn.call(grunt, grunt)后,我们就可以回头来看看我们的Gruntfile.js。

gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module.exports = function(grunt) { // ** wrapper function to define nodejs module**

// Project configuration.
grunt.initConfig({ //grunt.config
pkg: grunt.file.readJSON('package.json'), //grunt.file
uglify: { // task name
options: {
banner: 'banner'
},
build: { // target
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});

// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify'); //grunt.tasks

// Default task(s).
grunt.registerTask('default', ['uglify']); // grunt.tasks

};

2.1 Grunt.initConfig

初始化项目任务的配置信息,grunt.initConfig(configObject) == grunt.config.init(configObject)。这个configObject可以用grunt.config获得。

2.2 <%%> Template String

在配置各项任务时,其实发现有很多重复信息,包括上例中使用同样的pkg.name作为的源文件前缀。在Grunt配置文件中,<% %> 中的模板字符串可以使用文件任何一个配置属性包括文件路径,包信息、源/目标文件列表及路径等,从而减少不必要的重复配置,并使得相同的配置保持一致。

2.3 Tasks and Targets

任务包括单一任务和多目标任务。每个任务的配置对象可包括任务名称及不同的目标。任务和目标是类继承的关系,每个任务和每个目标都可以不同的配置项,而子目标的配置项会复写任务级别的配置项。

2.4 Options

每个任务配置对象都有一个缺省的options选项,options选项用来复写依赖的类库缺省的属性,比如uglify任务的option可以复写uglify2类库的banner配置。

2.5 Files

Grunt的任务大多都是基于文件操作,比如uglify是将文件压缩,concat是将一组文件合并为一个文件,jshint是对一组文件进行代码检查,所以任务的文件配置选项大多是针对源目标文件路径的定义以及文件的搜索匹配规则。Grunt在这一方面提供了强有力的支持,支持src-dest选项,有Compact、文件对象和文件数组三种方式。

2.5.1 Compcat Format

顾名思义简化版,是为了简单的只读任务使用,可以出现单个src-dest文件映射配置,比如jshint,只需定义source文件即可。

gruntfile.js
1
2
3
4
5
6
7
grunt.initConfig({
jshint: {
foo: {
src: ['src/aa.js', 'src/aaa.js']
}
}
});

2.5.2 Files Object Format & Files Array Format

这两种方式都指支持多个src-dest文件映射关系,在文件对象配置方式下,src/dest的文件信息是一个对象,对象的属性名称是目标文件,值是源文件;而 在文件数组配置方式下,src/dest的文件信息是一个数组,数组每个元素是一个对象,表示对应的src-dest映射;从而在第二种方式下课以应用诸如filter等其它属性。

cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
grunt.initConfig({
concat: {
foo: {
files: {
'dest/a.js': ['src/aa.js', 'src/aaa.js'],
'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'],
},
},
bar: {
files: [
{src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b/', nonull: true},
{src: ['src/bb1.js', 'src/bbb1.js'], dest: 'dest/b1/', filter: 'isFile'},
],,
},
},
});

2.6 Building files dynamically

在一些源文件列表搜索的复杂场景下,Grunt提供了一些额外的属性来更优雅的完成,这些属性需要打开expand = true开关。

  • cwd

    如果定义了cwd,那么所有的src都是相对于此路径(但是不包含)。

  • ext

    在生成目标文件时替换后缀。

  • flatten

    去除目标文件中的路径信息。

  • rename

    在扩展替换ext和flatten执行后,进行源文件的重命名并返回新的目标文件名,可能出现多个重名情况,也就会出现多个源文件。

2.7 Glob

这是当我看Gruntfile时最困惑的地方,{a,b}\/*.js。查了下文档,发现原来是来自nodejs内置的node-glob库支持,将几个和其它正则不一样的列在下面。

  • {} 逗号分隔的或操作

    {a,b}*.js ==> a.js || b.js

  • ** 所有包含路径符/

    lib/*/.js ==> lib/a.js and lib/sub/a.js

  • ! 排除

    !a*.js => 不以a字母开头的文件。

Reference

  1. Grunt官方文档任务配置
  2. Grunt API
  3. Grunt 源代码
Share Comments