在webpack大行其道的时间回过头来看看grunt任务流的打包方式
通过grunt配置让前端代码实现自动化压缩(minification)、编译、单元测试、linting等功能。
起步配置
先给出一个简单的传统前端项目,目录结构大致如下(后续内容会基于当前目录展开):
+-- index.html --- html页面
+-- src/ --- js文件
| --- index.js
| --- ...
+-- css/ --- css样式文件
| --- index.css
| --- ...
+-- common/ --- 公共信息
| +-- images --- 图片资源
| --- favicon.ico
| --- ...
| +-- modules --- 公共模块
| --- jquery.min.js
| --- ...
这是一个很传统简单的前端项目,接下来通过grunt为其配置压缩编译,lint等内容。
开始正式内容,确保电脑安装node6.0+版本。NodeJs
1.package.json
在项目根目录生成package.json文件:npm init,然后运行安装grunt相关npm包。
npm install grunt --save-dev
2.Gruntfile文件
同样在项目根目录新建Gruntfile.js文件,它是grunt的配置文件,用于任务配置。
Gruntfile由以下几部分构成:
- "wrapper" 函数
- 项目与任务配置
- 加载grunt插件和任务
- 自定义任务
GruntFile文件示例
module.exports = function(grunt) {
//通过配置pkg属性读取package.json对象,后续代码中可以通过<%=%>的方式访问使用对象和属性值
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/*.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});
// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
// 默认被执行的任务列表。
grunt.registerTask('default', ['uglify']);
};
针对上述文件,分部讲解每个版块内容:
"wrapper" 函数
每一份 Gruntfile (和grunt插件)都遵循同样的格式,你所书写的Grunt代码必须放在此函数内:
module.exports = function(grunt) {
// Do grunt-related things in here
};
项目与任务配置
grunt是以任务(task)流的方式进行文件处理的,大部分的Grunt任务都依赖某些配置数据,这些数据被定义在一个object内,并传递给grunt.initConfig 方法。
grunt.initConfig方法用于任务配置,grunt插件的配置。
pkg: grunt.file.readJSON('package.json') // 将存储在package.json文件中的JSON元数据引入到grunt config中
实例中我们引入了grunt-contrib-uglify插件,如果要在后续任务中使用该插件,需要在initConfig方法中配置对应属性,属性名必须和插件名一一对应:
我们指定了一个banner选项(用于在文件顶部生成一个注释),紧接着是一个单一的名为build的uglify目标,用于将一个js文件压缩为一个目标文件。
//我们在uglify插件中添加了两个配置:options和build
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
加载grunt插件和任务
grunt打包编译前端代码离不开插件,在上述initConfig方法里配置了插件信息后,需要通过loadNpmTasks方法把它们一个个引进来,代码如下:
//引入grunt-contrib-uglify插件
grunt.loadNpmTasks('grunt-contrib-uglify');
//引入grunt-contrib-jshint插件
grunt.loadNpmTasks('grunt-contrib-jshint');
一般编译过程中需要用到很多插件,一方面我们已经在package.json文件里写明安装了什么插件,另一方面我们又需要在grunt配置文件里一一引入。如果使用npm命令卸载模块以后,模块会自动从package.json文件中消失,但是必须手动从Gruntfile.js文件中清除,这样很不方便,一旦忘记,还会出现运行错误。这里有一个解决办法,就是安装load-grunt-tasks模块,然后在Gruntfile.js文件中,用下面的语句替代所有的grunt.loadNpmTasks语句。
require('load-grunt-tasks')(grunt);
这条语句的作用是自动分析package.json文件,自动加载所找到的grunt模块。
注意:模块的前缀如果是grunt-contrib,就表示该模块由grunt开发团队维护;如果前缀是grunt(比如grunt-pakmanager),就表示由第三方开发者维护。
自定义任务
语法:taskName:任务名 | taskList 参数必须是一个任务数组 | description:任务描述信息
grunt.task.registerTask(taskName, taskList) taskList中的所有任务都将按指定的顺序依次执行 grunt.task.registerTask(taskName, description, taskList)
回头查看上述GruntFile文件示例,我们在控制台直接输入grunt uglify是可以对src文件夹里的js压缩到build文件夹中的。(这里实际会依次执行grunt uglify:options和grunt uglify:build两条命令)。
我们会配置很多插件并通过loadNpmTasks引入,后续打包编译过程中如果一个个命令跑grunt 配置会显得繁琐,一个解决方法是自定义task内容,定制化执行grunt任务。
- 别名任务
一个可以参考的jshint配置示例如下: jshint配置
//options里指明了校验规则:eqeqeq表示要用严格相等运算符取代相等运算符,trailing表示行尾不得有多余的空格。
//files指明插件具体作用文件有哪些
jshint: {
options: {
eqeqeq: true,
trailing: true
},
files: ['Gruntfile.js', 'lib/**/*.js']
},
//default为grunt 运行默认名称
grunt.registerTask('default', ['jshint', 'uglify']);
//或者指定具体配置 grunt.registerTask('default', ['jshint', 'uglify:build']);
使用registerTask方法注册了一个名为default的task,后面通过数组方式引入两个在initConfig方法里定义的属性名jshint和uglify,之后我们直接运行grunt命令会执行jshint和uglify配置内容。
或者我们可以自定义名称个性化注册任务,可以直接通过grunt hint命令执行该任务。
//default为grunt 运行默认名称
grunt.registerTask('hint', ['jshint']);
- 任务函数
配置任务时可以抛开插件,使用函数直接编写特定任务,特定任务的属性和方法在任务函数内部通过this对象的属性即可访问。如果任务函数返回false表示任务失败。
下面这个案例中,当 Grunt 运行grunt foo:testing:123时,日志输出foo, testing 123。如果运行这个任务时不带参数,如grunt foo,日志输出foo, no args。
grunt.task.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);
}
});
进阶内容
1.多任务
当运行一个多任务时,Grunt会自动从项目的配置对象中查找同名属性。多任务可以有多个配置,并且可以使用任意命名的'targets'。 以下配置信息,当执行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.initConfig({
log: {
foo: [1, 2, 3],
bar: 'hello world',
baz: false
}
});
grunt.task.registerMultiTask('log', 'Log stuff.', function() {
grunt.log.writeln(this.target + ': ' + this.data);
});
2.内部任务和异步任务
大部分的contrib任务(主要是指官方提供的任务),包括 grunt-contrib-jshint插件的jshint任务,以及 grunt-contrib-concat插件的concat任务都是多任务形式的。
- 内部任务
如果你的任务并没有遵循 "多任务" 结构,那就使用自定义任务。并且在一个任务内部,你可以执行其他的任务:
grunt.registerTask('foo', 'My "foo" task.', function() {
// 顺序执行每个任务,uglify和jshint任务会在foo任务完成后一次执行
grunt.task.run('uglify', 'jshint');
// Or:
grunt.task.run(['uglify', 'jshint']);
});
- 异步任务
任务函数内部的this对象有一个async()方法。正常情况下grunt都是同步顺序执行task的,调用async方法后对应任务会触发异步机制。
//如果记录到任何错误,那么任务就会失败。
grunt.registerTask('foo', 'My "foo" task.', function() {
// Fail synchronously.
return false;
});
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() {
// Force task into async mode and grab a handle to the "done" function.
var done = this.async();
if (failureOfSomeKind) {
grunt.log.error('This is an error message.');
}
// 如果这个任务出现错误则返回false
if (ifErrors) { return false; }
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
//传递 false 给 done() 函数就会告诉Grunt你的任务已经失败,所有任务都会终止,除非指定 --force 。
setTimeout(function() {
grunt.log.writeln('All done!');
done(false);
}, 1000);
});
- 任务依赖
任务还可以依赖于其他任务的成功执行。注意 grunt.task.requires 并不会真正的运行其他任务,它仅仅检查其它任务是否已经执行。
grunt.registerTask('foo', 'My "foo" task.', function() {
return false;
});
grunt.registerTask('bar', 'My "bar" task.', function() {
// 如果"foo"任务运行失败或者没有运行则任务失败。
grunt.task.requires('foo');
// 如果"foo"任务运行成功则执行这里的代码。
grunt.log.writeln('Hello, world.');
});
// 用法:
// grunt foo bar
// 没有输出,因为"foo"失败。
// ***Note: This is an example of space-separated sequential commands,
// (similar to executing two lines of code: `grunt foo` then `grunt bar`)
// grunt bar
// 没有输出,因为"foo"从未运行。
- 工程化及后续
在package.json的script命令中配置具体grunt任务:
"scripts": {
"start": "grunt"
}
grunt插件目前有6000+,而且还在不断增加,这些插件可以帮助完整强大的工程化配置,一些常用的插件如下:
grunt-contrib-clean:删除文件。
grunt-contrib-compass:使用compass编译sass文件。
grunt-contrib-concat:合并文件。
grunt-contrib-copy:复制文件。
grunt-contrib-cssmin:压缩以及合并CSS文件。
grunt-contrib-imagemin:图像压缩模块。
grunt-contrib-jshint:检查JavaScript语法。
grunt-contrib-uglify:压缩以及合并JavaScript文件。
grunt-contrib-watch:监视文件变动,做出相应动作。
因为项目里还没具体实践过grunt工程化,笔记暂且到这,后续有内容再添加。