写在前面
特别注意:如果觉得本文不够详尽,强烈建议去看Grunt英文官网的教程,因为中文版的对注释做了部分翻译,不如不翻译(容易被翻译误导)
Grunt是什么?
Grunt是自动构建工具,类似的东西还有Ant、Buildy、Gmake等等,更多的信息请查看黯羽轻扬:JS自动化
Grunt有什么用?
自动构建工具能把自动化工具整合起来,以任务的形式管理,所以Grunt官网的副标题是The JavaScript Task Runner。
简单解释一下,构建项目 -> 模块化开发 -> 复用 -> 测试 -> 调试 -> 验证 -> 发布 -> 版本控制,这整个流程中需要用很多自动化工具,比如Require、QUnit、JSHint、Uglify等等。几乎每次修改源码都需要手动把某些工具按特定的顺序run again。。。
有了Grunt这样的自动构建工具后就可以简化run again的操作,写好配置文件之后用命令行运行自定义任务即可,甚至可以配合grunt-contrib-watch插件监听文件修改,自动运行任务
Grunt哪里好?
火:Twitter、JQuery、Adobe、Mozilla等等都在用Grunt
插件多:目前(2015.5.22)已经有4560个Grunt插件了,包括常用的JSHint、Require、Sass等等,大大地够用,实在不行还可以自己写插件
好用:配置文件比较简单,插件文档齐全(npm官网提供统一管理)
一.安装grunt
安装NodeJS
Windows直接去http://nodejs.org/下载安装包就好了,自带npm
其它平台的安装教程
装好之后命令行输入node -v测试一下
安装npm
命令行输入npm -v测试一下,如果出错的话,自己想办法去装npm
安装Grunt-CLI(命令行工具Command Line Interface)
命令行输入npm install -g grunt-cli等待安装完成即可
二.配置文件
需要两个配置文件:
package.json:用于Nodejs包管理,声明项目依赖模块(grunt以及grunt插件)
Gruntfile.js:Grunt配置文件,用来定义任务,可以叫Gruntfile.js或者Gruntfile.coffee
注意:package.json和Gruntfile.js都要放在项目的根目录下,与项目的源代码一起提交
说白了,学Grunt就是学怎么写配置文件
三.package.json
一般格式如下:
{
"name": "项目名称",
"version": "项目版本号",
"description": "项目描述",
"author": "项目创建者",
"license": "项目版权",
"devDependencies": {
//项目依赖插件
}
}
例如一个小项目的package.json:
{
"name": "world",
"version": "0.4.0",
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-jshint": "~0.6.3",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-watch": "^0.6.1"
}
}
写好package.json后,命令行cd进来,执行npm install,下载刚才声明的各个依赖项,会被放在当前目录下的node_modules文件夹里
需要添加依赖项的时候直接npm install grunt-contrib-XXX –save-dev就可以了,能够自动更新package.json的内容
注意:
不能有注释,否则npm install解析失败(无论是哪种形式的注释,无论放在文件首尾部分还是其它。。都不行)
version必须是X.X.X形式,X.X报错
其实version的格式有复杂的标准,请查看http://semver.org/
命令行输入npm install grunt-contrib-XXX –save-dev这样的命令可以自动更新package.json
最后一个依赖模块grunt-contrib-watch就是这样添进去的
版本号前面的~和^是什么意思?
没看到有资料解释这个,但我们可以猜:^表示会去找最新版本,而~表示指定版本(和git的HEAD^与HEAD~n可能一样。。当然,只是猜测)
一般都用~指定具体版本,因为可能存在插件兼容性以及稳定性问题
P.S.如果知道靠谱的解释的话请告诉我,谢谢
自动生成package.json
命令行输入npm init根据命令行提示一步一步完成,实测不好用,建议手写,或者写一份常用的模版,强行复用
三.Gruntfile.js
一般格式如下:
module.exports = function(grunt){
// 1. 定义任务
grunt.initConfig({
// 1. 读取package.json
pkg: grunt.file.readJSON('package.json'),
// 2. 初始化各个任务的配置对象
task1: {
options: {
// 设置配置选项
},
build: {
// 设置输入输出路径等等
}
},
task2: {
// ...
}
});
// 2. 加载插件
grunt.loadNpmTasks('Grunt插件名');
// 3. 注册任务
grunt.registerTask('default',['Grunt任务']);
grunt.registerTask('mytask',['task1', 'task3']);
};
例如一个小项目的package.json:
module.exports = function(grunt) {
// 1. 定义任务
grunt.initConfig({
// 1. 读取package.json
pkg: grunt.file.readJSON("package.json"),
// 2. 初始化各个任务的配置对象
// 合并文件
concat: {
options: {
// 防止合并出错(上一个文件尾部少了分号)
separator: ";",
// 顶部信息(需要自带注释格式,不自动注释,也不自动换行)
banner: "/*<%= pkg.name %>_<%= pkg.version %> " +
// *注意*:yyyy-mm-dd要加引号,表示字符串参数
"<%= grunt.template.today('yyyy-mm-dd') %>*/\r\n\r\n",
// 底部信息
footer: "\r\n\r\n/* author: http://ayqy.net/ */"
},
build: {
src: ["src/w.js", "src/Const.js", "src/Item.js", "src/Map.js", "src/Util.js", "src/Core.js"],
dest: "build/<%= pkg.name %>.js"
}
},
// 代码检查
jshint: {
options: {
eqeqeq: true, // 要求===
trailing: true, // 要求尾部无空格
//unused: true, // 要求警告没用到的变量(模块化代码会报错)
forin: true, // 要求for-in必须有hasOwnProp过滤
curly: true // 要求花括号
},
files: ["Gruntfile.js", "src/*.js"]
},
// 代码瘦身
uglify: {
options: {
// 不混淆变量名
mangle: false,
// 输出压缩率,可选的值有 false(不输出信息),gzip
report: "min",
// 顶部信息(需要自带注释格式,不自动注释,也不自动换行)
banner: "/*<%= pkg.name %>_<%= pkg.version %> " +
"<%= grunt.template.today('yyyy-mm-dd') %>*/\r\n\r\n",
// 底部信息
footer: "\r\n\r\n/* author: http://ayqy.net/ */"
},
build: {
files: {
// <%= concat.dist.dest %>表示uglify会自动瘦身concat任务中生成的文件
"build/<%= pkg.name %>.min.js": ["<%= concat.build.dest %>"]
}
}
},
// 监听文件变动,自动执行任务
watch: {
files: ["<%= jshint.files %>"],
tasks: ["default"]
}
});
// 2. 加载插件
grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-jshint");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-watch");
// 3. 注册任务
grunt.registerTask("default", ["jshint", "concat", "uglify"]); // 默认任务
grunt.registerTask("check", ["jshint"]); // 自定义任务:代码检查
};
写好Gruntfile.js后不需要执行num install之类的命令,直接用grunt TaskName执行对应的任务即可,比如:grunt执行default任务,grunt jshint执行代码检查,grunt mytask按顺序执行一连串的任务等等
如果有多个target的话可以用grunt TaskName:(英文半角冒号)TargetName执行指定的target,在注册任务的时候也可以用TaskName:TargetName指定target
P.S.至于target是什么,请往下看,配置Gruntfile.js可能有点麻烦,不过好在只用配置一次,以后直接用就可以了,可能对某些细节还不太理解,请务必看完下面的注意部分
注意:
dist和build到底用哪个?
常见的有options-dist和options-build两种,都可以用,因为名字无所谓
只有options是有所谓的,比如我们可以这样搞:
concat: { options: { separator: ";" }, xx: { src: "src/*.js", dest: "<%= pkg.name %>.js" } }
当然还可以这样搞:
concat: { options: { separator: ";" }, xx: { src: "src/*.js", dest: "<%= pkg.name %>.js" }, xxx: { src: "src/*.js", dest: "<%= pkg.name %>.js" } }
然后执行grunt concat,会依次执行xx和xxx,所以如果只有一个target(xx和xxx都叫target)的话,用build和dist没什么区别,因为不需要语义区分
如果有多个target,最好取一些语义友好的名字
P.S.个人更倾向于build,当然,名字不重要,所以某些教程里甚至出现了bar、foo之类的,让人费解
关于options
上面的例子说明grunt只认options(注意:少1个s都不行哟~),名字不是options的都一律当作target来执行
前面例子里都是target级的options,其实也可以有task级的options,可以对task下所有的target起作用,当然,不知道这个也没关系,多写点代码而已
注册同名任务会造成死递归
grunt.registerTask("concat", ["concat"]);
可以给任务取别名
grunt.registerTask("check", ["jshint"]); // 自定义任务:代码检查
更多的例子
如果需要更多的例子帮助理解,建议自己写个小项目,慢慢测试
如果实在没多少时间,请查看博客园:grunt使用小记之uglify:最全的uglify使用DEMO
四.在线资源
不知道哪个插件能满足需求,稳定,好用
http://www.gruntjs.net/plugins
链接页面给出了30天内下载量top100的插件,附有功能简介、最后更新时间等等,点击即可跳转至对应的npm官网插件主页。此外还支持搜索,非常方便
不知道XX插件有哪些配置选项
链接页面是npm官网,在搜索框里填插件名即可,例如grunt-contrib-watch
插件主页提供了详细的配置说明以及例子,比如watch的配置选项:
最简单的用法:
watch: { files: ['**/*'], tasks: ['jshint'] }
或者复杂的:
watch: { sass: { // We watch and compile sass files as normal but don't live reload here files: ['src/sass/*.sass'], tasks: ['sass'] }, livereload: { // Here we watch the files the sass task will compile to // These files are sent to the live reload server after sass compiles to them options: { livereload: true }, files: ['dest/**/*'] } }
Grunt API
链接页面有Grunt的所有API,比如grunt.log、grunt.initConfig等等等等,看到新东西就去查吧
参考资料
W3CPlus:Grunt教程——初涉Grunt:比大多数入门教程好很多
Grunt中文官网:不推荐
Grunt英文官网:强烈推荐
博客园:【grunt整合版】30分钟学会使用grunt打包前端代码:如果耐得住寂寞可以看这个。。