写在前面
特别注意:如果觉得本文不够详尽,强烈建议去看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打包前端代码:如果耐得住寂寞可以看这个。。