之前博客中大致描述过“前端自动化构建工具Grunt”及“grunt[mismatched:define]”等信息。然而,并没有深入;下述内容,将深入剖析Grunt Files处理方式、配置项、自定义插件。
一、准备工作
A. 通过npm init
在项目根目录下生成package.json
;
B. 通过npm install grunt --save-dev
安装grunt依赖;
C. 项目根目录下手动创建文件夹Gruntfile.js
Gruntfile由以下几部分构成: ① “wrapper” 函数 ② 项目与任务配置 ③ 加载grunt插件和任务 ④ 自定义任务
1. Wrapper函数
每一个Gruntfile文件遵循同样的格式,所有代码必须包裹在下述函数中:
代码语言:javascript复制module.exports = function(grunt){
// grunt相关其他信息
};
2. 项目与任务配置
Grunt的task配置都是在 Gruntfile 中的grunt.initConfig
方法中指定的。
grunt.initConfig({
copy: {}
});
3. 加载Grunt插件和任务
只要在 package.json
文件中被列为dependency(依赖)的包,并通过npm install
安装之后,都可以在Gruntfile中以简单命令的形式使用:
// 加载能够提供"copy"任务的插件。
grunt.loadNpmTasks('grunt-contrib-copy');
可以通过“load-grunt-tasks”进行一次性载入所有插件,无需依依引入。
代码语言:javascript复制require('load-grunt-tasks')(grunt);
4. 自定义任务
(1)自定义任务
代码语言:javascript复制grunt.registerTask(taskName, [description, ] taskList)
grunt.registerMultiTask(taskName, [description, ] taskFunction)
示例:
代码语言:javascript复制grunt.registerTask('cunstom-copy', ['copy:js']);
grunt.registerMultiTask('default', ['clean', 'copy']);
注意:但是其自定义name(“cunstom-copy”)不能为上述“原子”任务名称,否则会造成死循环!导致“Maximum call stack size exceeded”问题! (2)基本任务 执行指定的任务函数,并传递任何使用冒号分割的参数作为函数的参数。
代码语言:javascript复制grunt.registerTask(taskName, [description, ] taskFunction)
示例:
代码语言:javascript复制grunt.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) {
grunt.log.writeln(arg1 "," arg2);
// 内部通过run执行其他任务
grunt.task.run('bar', 'baz');
});
二、配置任务
grunt.initConfig
方法中指定任务配置,主要是以任务名称命名的属性,也可以包含其他任意数据。在grunt.initConfig({})
中不针对任何Task的属性,Grunt会将作为常量进行储存,以备后续通过<%= %>
引用。
module.exports = function(grunt){
var config = {
app: 'app', // 源文件目录
dest: 'dest' // 目标文件目录
};
grunt.initConfig({
config: config,
pkg: grunt.file.readJSON('package.json'),
copy: {
js: {
expand: true,
cwd: '<%= config.app %>/',
src: ['js/**/*.js'],
dest: '<%= config.dest %>/dest/'
}
}
});
// 加载插件
grunt.loadNpmTasks('grunt-contrib-copy')
};
- config和pkg为常量,其中config在文件开头处定义方便统一,便于后续修改。
- copy为Task,加载“grunt-contrib-copy”插件
- copy中的js为Target,可以通过grunt copy:js执行
通配符说明:
通配符 | 说明 |
---|---|
* | 匹配任意数量的字符,但不匹配 / |
? | 匹配单个字符,但不匹配 / |
** | 匹配任意数量的字符,包括 /,只要它是路径中唯一的一部分 |
{} | 允许使用一个逗号分割的“或”表达式列表 |
! | 在模式的开头用于排除一个匹配模式所匹配的任何文件 |
A. foo/*.js
将匹配位于foo/目录下的所有的.js
结尾的文件;
B. foo/**/*js
将匹配foo/目录以及其子目录中所有以.js
结尾的文件。
模板说明:
使用<%= %>
分隔符指定的模板会在任务从它们的配置中读取相应的数据时将自动扩展扫描。在模板中使用grunt以及它的方法都是有效的,例如: <%= grunt.template.today('yyyy-mm-dd') %>
1. 任务配置和目标
当运行一个任务时,Grunt会自动查找配置对象中的同名属性。
grunt copy
会自动匹配initConfig()
方法中的copy属性,在不指定target情况下,默认将遍历所有目标(target)并依次处理。也可以同时指定任务(task)和目标(target),grunt copy:js
将只会处理指定目标(target)的配置。
2. options属性
在一个任务配置中,options属性可以用来指定覆盖内置属性的默认值。此外,每一个目标(target)中还可以拥有一个专门针对此目标(target)的options属性。注意,options对象是可选的,如果不需要,可以忽略。
代码语言:javascript复制grunt.initConfig({
concat: {
options: {
// 这里是任务级的Options,覆盖默认值
},
foo: {
options: {
// "foo" target options may go here, overriding task-level options.
},
},
bar: {
// No options specified; this target will use task-level options.
},
},
});
3. 文件
由于大多的任务都是执行文件操作,Grunt有一个强大的抽象层用于声明任务应该操作哪些文件。 (1)简洁格式 每个目标对应一个src-dest文件映射
代码语言:javascript复制copy: {
js: {
src: ['<%= config.app %>/js/**/*.js'],
dest: '<%= config.dest %>/'
},
html: {
src: '<%= config.app %>/html/**/*.html',
dest: '<%= config.dest %>/'
}
}
(2)文件对象格式 每个目标对应多个src-dest形式的文件映射,属性名就是目标文件,源文件就是它的值(源文件列表则使用数组格式声明)。
代码语言:javascript复制copy: {
'<%= config.dest %>/': ['<%= config.app %>/js/**/*.js', '<%= config.app %>/html/**/*.html']
}
(3)文件数组格式 每个目标对应多个src-dest文件映射
代码语言:javascript复制copy: {
bulid: {
files: [{ src: ['<%= config.app %>/js/**/*.js'], dest: '<%= config.dest %>/'},
{ src: '<%= config.app %>/html/**/*.html', dest: '<%= config.dest %>/'}]
}
}
“简洁格式”和“文件数组格式”可以支持下列属性:
属性 | 说明 |
---|---|
filter | 它通过接受任意一个有效的fs.Stats方法名或者一个函数来匹配src文件路径并根据匹配结果返回true或者false |
nonull | 如果被设置为true,未匹配的模式也将执行。结合Grunt的–verbore标志, 这个选项可以帮助用来调试文件路径的问题 |
dot | 它允许模式模式匹配句点开头的文件名,即使模式并不明确文件名开头部分是否有句点 |
matchBase | 如果设置这个属性,缺少斜线的模式(意味着模式中不能使用斜线进行文件路径的匹配)将不会匹配包含在斜线中的文件名。 例如,a?b将匹配/xyz/123/acb但不匹配/xyz/acb/123 |
expand | 处理动态的src-dest文件映射 |
示例:filter
代码语言:javascript复制clean: {
foo: {
src: ['tmp/**/*'],
filter: 'isFile',
},
bar: {
src: ['tmp/**/*'],
filter: function(filepath) {
return (grunt.file.isDir(filepath)
&& require('fs').readdirSync(filepath).length === 0);
},
}
}
其中isFile为fs.Stats中的方法,可以直接引用!当然,也可以自定义filter,其函数第一个参数为当前文件路径,上述自定义函数为清空空文件夹。
当你希望处理大量的单个文件时,可以通过一些附加的属性来动态的构建一个文件列表。前提,expand 设置为true:
属性 | 说明 |
---|---|
cwd | 所有src指定的匹配都将相对于此处指定的路径(但不包括此路径) |
src | 相对于cwd路径的匹配模式 |
dest | 目标文件路径前缀 |
ext | 对于生成的dest路径中所有实际存在文件,均使用这个属性值替换扩展名 |
extDot | 用于指定标记扩展名的英文点号的所在位置。可以赋值 ‘first’ (扩展名从文件名中的第一个英文点号开始) 或 ‘last’ (扩展名从最后一个英文点号开始),默认值为 ‘first’ [添加于 0.4.3 版本] |
flatten | 从生成的dest路径中移除所有的路径部分 |
rename | 对每个匹配的src文件调用这个函数(在重命名后缀和移除路径之后)。dest和匹配的src路径将被作为参数传入,此函数应该返回一个新的dest值。 如果相同的dest返回不止一次,那么,每个返回此值的src来源都将被添加到一个数组中作为源列表 |
三、补充:
1. grunt支持异步等写法,请参考其API
2. 创建问题,请参考其常见问题
3. 可以通过插件“time-grunt”进行统计每个Task执行时间
代码语言:javascript复制require('time-grunt')(grunt);