有一个 SDK 项目是使用的 Grunt 打包,最近在梳理代码,也"相对"深入的看一下 Grunt 到底是个什么东东。之所以是从 0 到 0.8 是因为目前只是看完概念,写 demo, 也能看懂项目上的配置,但是没有真正在在项目上自己配置过,还有一些插件开发,模板,高级 API 也是随便看了看,没有深入。
其实 Grunt 的官方文档也得还挺不错的,我这里也就勉强算个笔记吧!想学 Grunt 还是建议大家看官网的吧,如果是基本配置的话用不了多长时间的。
本文分为四个部分讲解 Grunt,请按需 PICK。
概述
Grunt 是一个 JS 构建工具。可以实现代码拼接,压缩,编译,单元测试等任务,并可以进行自动化操作。Grunt 有很多丰富的插件,其中有官方维护的,还有开发者维护的,可以用它来拓展功能。
Grunt 目前稳定版是 1.1.0。
安装
首先使用 npm 将 grunt-cli 安装到全局。
安装 cli 不等于安装了 Grunt! 它只是调用与 Gruntfile 在同一目录的 Grunt。通过这种设计,允许你在同一个系统上同时安装多个版本的 Grunt。
npm install -g grunt-cli
grunt 和 grunt 插件都可以使用 npm/yarn 安装,安装到 devDependencies 中。
一个简单的 Grunt 项目
一个简单的 Grunt 项目的话有 Gruntfile 和 package.json 即可。package.json 大家都熟,不做太多描述。我们主要来了解一下 Gruntfile。
Gruntfile 由四部分构成:
- "wrapper" 函数
- 项目与任务配置
- 加载 grunt 插件和任务
- 自定义任务
先看一个 Gruntfile 实例对其有一个大概的印象,然后我们再分步骤讲解。
module.exports = function(grunt) {
// 项目配置
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
}
});
// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
// 默认被执行的任务列表。
grunt.registerTask('default', ['uglify']);
};
"wrapper" 函数
module.exports = function(grunt) {
// Do grunt-related things in here
};
每一份 Gruntfile 都遵循同样的格式,你所书写的 Grunt 代码必须放在此函数中。
项目和任务配置
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
},
meta: { name: 'test' }
});
传给 initConfig 的对象是任务配置或其他任意数据。上面例子中
- pkg 是读取的 package.json 数据。
- uglify 是 grunt-contrib-uglify 的任务
- meta 是自定义的数据
其中任务可以是外部的 npm 依赖对应的任务,也可能是我们自定义的多任务。
在任务中可以访问其他任务/数据中定义的变量
多任务
多任务可以通过任意命名的目标(target)来定义多个配置
grunt.initConfig({
concat: {
foo: {
// 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.
},
},
});
options
在一个任务配置中,options 属性可以用来指定覆盖内置属性的默认值。target 级的 options 会覆盖 task 级的 options。
options 对象是可选的,如果不需要,可以忽略。
文件操作
由于大多任务都是执行文件操作,Grunt 有一个强大的抽象层用于声明任务应该操作哪些文件。Grunt 提供了多种定义 src-dest 文件映射的方式。这里只列出一个大概,在官网有详细描述。
- 简洁格式
- 文件对象格式
- 文件数组格式
- 自定义过滤函数
- 通配符模式
- 动态构建文件对象
- 较老的格式(不推荐)
模板语言
配置可以使用模板语言,可以调用任何配置中的变量,还可以像上面的例子中一样引入外部数据。
加载 grunt 插件和任务
// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
从指定的 Grunt 插件中加载任务。
自定义任务
用户可以在项目中自定义任务。
- 按照配置个数可以分为: 单任务和多任务。
- 按照执行可以分为: 同步任务和异步任务。
还可以设置任务别名为指定一组任务。执行该任务别名,就代表其指定的一组任务。
在任务中可以
- 访问自身的名称和参数。
- 执行其他任务
- 检查其他任务的执行状态
- 检查配置是否存在
- 读取配置属性。
/**
* 基本自定义任务
*/
// !! 基本任务无需配置
// 执行 grunt foo 打印 foo, no args
// 执行 grunt foo:arg1 打印 foo, arg1 undefined
// 执行 grunt foo:arg1:arg2 打印 foo, arg1 arg2
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);
}
});
/**
* 多任务
*/
// !! 多任务必须在配置,否则报错
// >> No "log" targets found.
// Warning: Task "log" failed. Use --force to continue.
// Aborted due to warnings.
grunt.registerMultiTask('log', 'Log stuff.', function() {
// 执行 grunt log:foo, 打印 foo: 1,2,3
// 执行 grunt log:bar, 打印 bar: hello world
grunt.log.writeln(this.target + ': ' + this.data);
});
/**
* 找不到任务名
*/
// 执行 grunt fo
// 报错
// Warning: Task "fo" not found. Use --force to continue.
// Aborted due to warnings.
/**
* 在任务内部调用其他任务
*/
grunt.registerTask('custom1', function () {
// grunt.task.run('custom');
// 或
// grunt.task.run('concat', 'jshint');
// 或
grunt.task.run(['concat', 'jshint']); // 这个写法与上面的那种写法一致
});
/**
* 异步任务
*/
grunt.registerTask('custom2', function() {
// 需要这一行告知 grunt 这是 async 模式并返回 done 方法
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();
}, 2000);
});
/**
* 任务出错
*/
// 任务出错,返回 false 后续任务都会被终止,除非指定 --force
// 不返回或返回其他值都没事
grunt.registerTask('a1', function() {
grunt.log.writeln('执行 a1');
return false;
});
grunt.registerTask('a11', function () {
var done = this.async();
setTimeout(function () {
console.log('执行 a11');
done(0);
});
});
grunt.registerTask('a2', function() {
grunt.log.writeln('执行 a2');
return true;
});
grunt.registerTask('a', ['a1', 'a11', 'a2']);
/**
* 依赖于其他任务成功执行
*/
grunt.registerTask('b1', function() {
grunt.task.requires('a11');
grunt.log.writeln('执行 b1');
return true;
});
grunt.registerTask('b', ['a11', 'b1']);
/**
* 依赖于属性
*/
grunt.registerTask('c1', function() {
// grunt.config.requires('meta.name1');
grunt.config.requires(['meta', 'name']); // 这两种写法是相同的
grunt.log.writeln('执行 c1');
return true;
});
/**
* 访问属性
*/
grunt.registerTask('d1', function() {
grunt.log.writeln('执行 d1', grunt.config('meta'));
grunt.log.writeln('执行 d1 name1', grunt.config('meta.name1'));
grunt.log.writeln('执行 d1 name', grunt.config(['meta', 'name']));
return true;
});
/**
* 任务别名
*/
// 定义 default 任务别名,执行 default 就是依次执行后面列表中的任务
// default 可以不传 直接执行 grunt
grunt.registerTask('default', ['concat', 'uglify', 'jshint']);
// 执行 grunt custom
grunt.registerTask('custom', ['concat', 'uglify', 'jshint']);
// 可以执行 grunt concat 只执行 concat 任务
其他
上述是 Grunt 的基本使用,除此之外还可以