安装
- 安装cli npm install -g grunt-cli
- 有package.json 和 Gruntfile 文件后 grunt构建
- package.json: 项目依赖的grunt和Grunt插件,放置于devDependencies配置段内。
npm install
安装其中依赖 - Gruntfile: 此文件被命名为 Gruntfile.js 或 Gruntfile.coffee,用来配置或定义任务(task)并加载Grunt插件的。 此文档中提到的 Gruntfile 其实说的是一个文件,文件名是 Gruntfile.js 或 Gruntfile.coffee
- package.json应当放置于项目的根目录中,与Gruntfile在同一目录中,并且应该与项目的源代码一起被提交
- package.json: 项目依赖的grunt和Grunt插件,放置于devDependencies配置段内。
package.json
创建grunt-init
自动创建npm init
创建- 相关配置信息
{
"name": "my-project-name",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.5",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-nodeunit": "~0.4.1",
"grunt-contrib-uglify": "~0.5.0"
}
}
- 安装grunt和 其插件
- 安装Grunt最新版本到项目目录中,并将其添加到devDependencies内:
npm install grunt --save-dev
- grunt插件和其它node模块都可以按相同的方式安装。下面展示的实例就是安装 JSHint 任务模块
npm install grunt-contrib-jshint --save-dev
- Gruntfile
- Gruntfile.js 或 Gruntfile.coffee 文件是有效的 JavaScript 或 CoffeeScript 文件,应当放在你的项目根目录中,和package.json文件在同一目录层级,并和项目源码一起加入源码管理器
- 组成
- “wrapper” 函数
- 项目与任务配置
- 加载grunt插件和任务
- 自定义任务
module.exports = function(grunt) {
//"wrapper" 函数
/* module.exports = function(grunt) {
Do grunt-related things in here
*/};
//项目与任务配置
// Project configuration.
grunt.initConfig({
//将存储在package.json文件中的JSON元数据引入到grunt config中
pkg: grunt.file.readJSON('package.json'),
//自己定义任务或者插件
uglify: {
options: {
//<% %>模板字符串可以引用任意的配置属性,因此可以通过这种方式来指定诸如文件路径和文件列表类型的配置数据,从而减少一些重复的工作
//用于在文件顶部生成一个注释
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
//用于将一个js文件压缩为一个目标文件
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});
//加载grunt插件和任务
// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
//自定义任务
// 默认被执行的任务列表。
grunt.registerTask('default', ['uglify']);
/*
//可以定义在外部.js 文件中,并通过grunt.loadTasks 方法加载
grunt.registerTask('default', 'Log some stuff.', function() {
grunt.log.write('Logging some stuff...').ok();
});
*/
};
- Grunt配置:Grunt的task配置都是在 Gruntfile 中的
grunt.initConfig
方法中指定的 - 任务配置和目标: concat任务有名为foo和bar两个目标,而uglify任务仅仅只有一个名为bar目标。
grunt.initConfig({
concat: {
options: {
// 这里是任务级的Options,覆盖默认值
},
foo: {
options: {
// "foo" target options may go here, overriding task-level options.
},
// concat task "foo" target options and files go here.
},
bar: {
// concat task "bar" target options and files go here.
},
},
uglify: {
bar: {
// uglify task "bar" target options and files go here.
},
},
});
grunt concat:foo
或者grunt concat:bar
,将只会处理指定目标(target)的配置,而运行grunt concat
将遍历所有目标(target)并依次处理- options属性:任务的叫任务级的Options 目标的叫目标级的Options
- 目标级的options将会覆盖任务级的options
- 文件:src-dest(源文件-目标文件)文件映射的方式
- filter:接受任意一个有效的fs.Stats方法名或者一个函数来匹配src文件路径并根据匹配结果返回true或者false
- nonull 如果被设置为 true,未匹配的模式也将执行。结合Grunt的–verbore标志, 这个选项可以帮助用来调试文件路径的问题
- dot 它允许模式模式匹配句点开头的文件名,即使模式并不明确文件名开头部分是否有句
- matchBase如果设置这个属性,缺少斜线的模式(意味着模式中不能使用斜线进行文件路径的匹配)将不会匹配包含在斜线中的文件名。 例如,a?b 将匹配 /xyz/123/acb 但不匹配 /xyz/acb/123。
- expand 处理动态的src-dest文件映射,更多的信息请查看动态构建文件对象。
- 目标级的options
grunt.initConfig({
jshint: {
ignore_warning: {
options: {
'-W015': true,
},
src: 'js/**',
filter: 'isFile'
}
}
});
- 简洁格式:
grunt.initConfig({
jshint: {
foo: {
src: ['src/aa.js', 'src/aaa.js']
},
},
concat: {
bar: {
src: ['src/bb.js', 'src/bbb.js'],
dest: 'dest/b.js',
},
},
});
- 文件对象格式
- 每个目标对应多个src-dest形式的文件映射,属性名就是目标文件,源文件就是它的值(源文件列表则使用数组格式声明)
- 可以使用这种方式指定数个src-dest文件映射, 但是不能够给每个映射指定附加的属性。
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: {
'dest/b.js': ['src/bb.js', 'src/bbb.js'],
'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'],
},
},
},
});
- 文件数组格式
- 这种形式支持每个目标对应多个src-dest文件映射,同时也允许每个映射拥有额外属性:
grunt.initConfig({
concat: {
foo: {
files: [
{src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
{src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.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'},
],
},
},
});
- 自定义过滤函数
- 下面的配置仅仅清理一个与模式匹配的真实的文件:
grunt.initConfig({
clean: {
foo: {
src: ['tmp/**/*'],
filter: 'isFile',
},
},
});
- 创建自己的函数
grunt.initConfig({
clean: {
foo: {
src: ['tmp/**/*'],
filter: function(filepath) {
return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0);
},
},
},
});
- 通配符模式
*
匹配任意数量的字符,但不匹配/
?
匹配单个字符,但不匹配/
**
匹配任意数量的字符,包括/
,只要它是路径中唯一的一部分{}
允许使用一个逗号分割的“或”表达式列表!
在模式的开头用于排除一个匹配模式所匹配的任何文件
-foo/*.js
将匹配位于foo/
目录下的所有的.js
结尾的文件;而foo/**/*js
将匹配foo/
目录以及其子目录中所有以.js
结尾的文件
// 指定单个文件:
{src: 'foo/this.js', dest: ...}
// 指定一个文件数组:
{src: ['foo/this.js', 'foo/that.js', 'foo/the-other.js'], dest: ...}
// 使用一个匹配模式:
{src: 'foo/th*.js', dest: ...}
// 一个独立的node-glob模式:
{src: 'foo/{a,b}*.js', dest: ...}
// 也可以这样编写:
{src: ['foo/a*.js', 'foo/b*.js'], dest: ...}
// foo目录中所有的.js文件,按字母顺序排序:
{src: ['foo/*.js'], dest: ...}
// 首先是bar.js,接着是剩下的.js文件,并按字母顺序排序:
{src: ['foo/bar.js', 'foo/*.js'], dest: ...}
// 除bar.js之外的所有的.js文件,按字母顺序排序:
{src: ['foo/*.js', '!foo/bar.js'], dest: ...}
// 按字母顺序排序的所有.js文件,但是bar.js在最后。
{src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...}
// 模板也可以用于文件路径或者匹配模式中:
{src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}
// 它们也可以引用在配置中定义的其他文件列表:
{src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}
- 动态构建文件对象
- 当你希望处理大量的单个文件时,这里有一些附加的属性可以用来动态的构建一个文件列表
uglify
任务中的static_mappings
和dynamic_mappings
两个目标具有相同的src-dest文件映射列表, 这是因为任务运行时Grunt会自动展开dynamic_mappings
文件对象为4个单独的静态src-dest文件映射
grunt.initConfig({
uglify: {
static_mappings: {
// Because these src-dest file mappings are manually specified, every
// time a new file is added or removed, the Gruntfile has to be updated.
files: [
{src: 'lib/a.js', dest: 'build/a.min.js'},
{src: 'lib/b.js', dest: 'build/b.min.js'},
{src: 'lib/subdir/c.js', dest: 'build/subdir/c.min.js'},
{src: 'lib/subdir/d.js', dest: 'build/subdir/d.min.js'},
],
},
dynamic_mappings: {
// Grunt will search for "**/*.js" under "lib/" when the "uglify" task
// runs and build the appropriate src-dest file mappings then, so you
// don't need to update the Gruntfile when files are added or removed.
files: [
{
expand: true, // Enable dynamic expansion.
// cwd 所有src指定的匹配都将相对于此处指定的路径(但不包括此路径)
cwd: 'lib/', // Src matches are relative to this path.
//src 相对于cwd路径的匹配模式
src: ['**/*.js'], // Actual pattern(s) to match.
//dest 目标文件路径前缀。
dest: 'build/', // Destination path prefix.
//ext 对于生成的dest路径中所有实际存在文件,均使用这个属性值替换扩展名
ext: '.min.js', // Dest filepaths will have this extension.
//extDot 用于指定标记扩展名的英文点号的所在位置
// 以赋值 'first' (扩展名从文件名中的第一个英文点号开始) 或 'last' (扩展名从最后一个英文点号开始),默认值为 'first'
extDot: 'first' // Extensions in filenames begin after the first dot
},
],
},
},
});
flatten
从生成的dest路径中移除所有的路径部分rename
定制函数,返回新文件名字符串,每次匹配目标文件调用函数
grunt.initConfig({
//copy任务
copy: {
//backup目标
backup: {
//构建文件对象
files: [{
//动态构建
expand: true,
//源文件
src: ['docs/README.md'], // The README.md file has been specified for backup
//目标文件
rename: function () { // The value for rename must be a function
return 'docs/BACKUP.txt'; // The function must return a string with the complete destination
}
}]
}
}
});
dev
目录下的所有文件处理到dist
目录下,文件名中有beta
的删除
grunt.initConfig({
copy: {
production: {
files: [{
expand: true,
cwd: 'dev/',
src: ['*'],
dest: 'dist/',
rename: function (dest, src) { // The `dest` and `src` values can be passed into the function
return dest + src.replace('beta',''); // The `src` is being renamed; the `dest` remains the same
}
}]
}
}
});
- 模板
- 使用
<% %>
分隔符指定的模板会在任务从它们的配置中读取相应的数据时将自动扩展扫描 <% %>
执行任意内联的JavaScript代码<%= prop.subprop %>
将会自动展开配置信息中的prop.subprop的值- 运行
grunt concat:sample
时将通过banner中的/* abcde */
连同foo/*.js
+bar/*.js
+bar/*.js
匹配的所有文件来生成一个名为build/abcde.js
的文件。
grunt.initConfig({
//任务
concat: {
//目标
sample: {
options: {
//文件顶部生成注释
banner: '/* <%= baz %> */\n', // '/* abcde */\n'
},
//源文件
src: ['<%= qux %>', 'baz/*.js'], // [['foo/*.js', 'bar/*.js'], 'baz/*.js']
//目标文件
dest: 'build/<%= baz %>.js', // 'build/abcde.js'
},
},
//模板
//用于任务配置模板的任意属性
foo: 'c',
bar: 'b<%= foo %>d', // 'bcd'
baz: 'a<%= bar %>e', // 'abcde'
qux: ['foo/*.js', 'bar/*.js'],
});
-导入外部数据
- 项目的元数据是从package.json文件中导入到Grunt配置中的,并且grunt-contrib-uglify 插件中的 uglify 任务被配置用于压缩一个源文件以及使用该元数据动态的生成一个banner注释
- -Grunt有
grunt.file.readJSON
和grunt.file.readYAML
两个方法分别用于引入JSON和YAML数据。
grunt.initConfig({
//元数据是从package.json文件中导入
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
//文件顶部生成注释
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
dist: {
src: 'src/<%= pkg.name %>.js',
dest: 'dist/<%= pkg.name %>.min.js'
}
}
});
Gruntfile 实例
// "wrapper"函数
module.exports = function(grunt) {
//初始化配置
grunt.initConfig({
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
options: {
globals: {
jQuery: true
}
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint']
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['jshint']);
};
- 创建任务
- 没有指定一个任务,并且你已经定义一个名为 “default” 的任务,那么该任务将会默认被执行
-taskList
中指定的每个任务都会按照其出现的顺序依次执行。taskList
参数必须时一个任务数组
grunt.registerTask(taskName, [description, ] taskList)
//定义了一个 'default' 任务,如果运行Grunt时没有指定任何任务,它将自动执行'jshint'、'qunit'、'concat' 和 'uglify' 任务。
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
//别名 "dist" 将执行 "concat" 和 "uglify" 两个任务,并且它们都带有一个 "dist" 参数:
grunt.registerTask('dist', ['concat:dist', 'uglify:dist']);
- 多任务
grunt.registerMultiTask(taskName, [description, ] taskFunction)
grunt.initConfig({
log: {
//target : data
foo: [1, 2, 3],
bar: 'hello world',
baz: false
}
});
grunt.registerMultiTask('log', 'Log stuff.', function() {
grunt.log.writeln(this.target + ': ' + this.data);
});
//grunt log:foo => foo: 1,2,3
//grunt log:bar => bar: hello world
//grunt log=> foo: 1,2,3,然后是bar: hello world,最后是baz: false
grunt.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) {
if (arguments.length === 0) {
grunt.log.writeln(this.name + ", no args");
} else {
grunt.log.writeln(this.name + ", " + arg1 + " " + arg2);
}
});
//grunt foo:testing:123 => foo, testing 123
//grunt foo => foo, no args
- 自定义任务
grunt.registerTask('default', 'My "default" task description.', function() {
grunt.log.writeln('Currently running the "default" task.');
});
//在一个任务内部,你可以执行其他的任务。
grunt.registerTask('foo', 'My "foo" task.', function() {
// Enqueue "bar" and "baz" tasks, to run after "foo" finishes, in-order.
grunt.task.run('bar', 'baz');
// Or:
grunt.task.run(['bar', 'baz']);
});
- 异步任务
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() {
// Force task into async mode and grab a handle to the "done" function.
var done = this.async();
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
setTimeout(function() {
grunt.log.writeln('All done!');
done();
}, 1000);
});
- 任务也可以访问它们自身名称和参数
grunt.registerTask('foo', 'My "foo" task.', function(a, b) {
grunt.log.writeln(this.name, a, b);
});
// 用法:
// grunt foo
// logs: "foo", undefined, undefined
// grunt foo:bar
// logs: "foo", "bar", undefined
// grunt foo:bar:baz
// logs: "foo", "bar", "baz"