Grunt的安装和使用

安装Grunt前的准备

本教程的内容较少原创,多从其他文档上摘录,详见参考资料部分。

Grunt是一个自动化的项目构建工具。Grunt和Grunt的插件都是通过Node.js的包管理器npm来安装和管理的。

如果还没有安装Node.js,需要先从Node.js网站(http://nodejs.org/)下载安装。

Node.js安装中,以及后继的其他安装中,可能需要系统管理员权限。

安装Grunt之前,可以在命令行中运行node -v查看你的Node.js版本。0.8.0及以上版本的Node.js才能很好的运行Grunt。

安装Grunt的命令行接口

为了方便使用Grunt,你应该在全局范围内安装Grunt的命令行接口(CLI)。

在命令行中,执行如下命令:

npm install -g grunt-cli

这条命令将会把grunt命令植入到你的系统路径中,这样就允许你从任意目录来运行它(定位到任意目录运行grunt命令)。

认识Grunt的两个常用文件

使用Grunt构建的项目中,大多包含有package.json和Gruntfile。

package.json:用于存储已经作为npm模块发布的项目元数据(也就是依赖模块,包括Grunt本身)。

Gruntfile.js:用于配置或者定义Grunt任务和加载Grunt插件。

用一个现有的Grunt项目进行工作

如果已经安装好了Grunt的命令行接口(CLI),并且项目中已经有了package.json和Gruntfile,使用Grunt进行工作是非常容易的。

安装Grunt和相关Grunt插件

  1. 进入项目目录(在命令行窗口定位到项目目录;在windows系统下,也可以进入项目文件夹后,按Shift+鼠标右键,打开右键菜单,选择“在此处打开命令窗口(W)”)。
  2. 在命令行中,运行 npm install,安装项目相关依赖(插件,Grunt内置任务等依赖)。

有些Grunt插件,比如JSDoc3插件,需要在命令行中可以执行git命令。最好先安装一个msysgit(http://msysgit.github.io/),然后在环境变量中增加git的bin目录。

执行Grunt构建

在上述的命令行中执行 grunt,就可以运行Grunt构建了。

至此,通过安装Node.js、在命令行中运行 npm install -g grunt-clinpm installgrunt三个命令,就完成了使用现有的Grunt项目进行工作的所有准备。

准备一个新的Grunt项目

快速工作的项目脚手架

有很多项目的结构和内容都很类似,人们就设计开发了可以快速工作的模板,能够通过模板迅速的自动创建项目,称为脚手架工具(grunt-init)。

在Grunt的GitHub主页(https://github.com/gruntjs)上有很多之用的 grunt-init-* 脚本架模板工具,比如grunt-init-jquery、grunt-init-gruntplugin等等。

安装grunt-init

grunt-init 是一个用于自动创建项目的脚手架工具。

先在命令行中进行全局安装:

npm install -g grunt-init

这样,会把grunt-init命令植入到你的系统路径,从而允许你在任何目录中都可以运行它。

安装和使用grunt-init模板

把需要的模板放在你的 ~/.grunt-init/ 目录中(在Windows平台是%USERPROFILE%/.grunt-init目录)。

%USERPROFILE% 一般是指“C:\Users\你的用户名”这个目录。

建议使用如下的git clone命令把这个模板克隆到该目录:

git clone https://github.com/clientlab/grunt-init-gruntfile.git ~/.grunt-init/gruntfile

在Windows平台上稍微有些不方便,但是可以在文件浏览器中输入%USERPROFILE%,定义到该目录下,然后执行如下命令:

git clone https://github.com/clientlab/grunt-init-gruntfile.git ./.grunt-init/gruntfile

要想使用上述命令,需要先安装一个msysgit(http://msysgit.github.io/),然后在环境变量中增加git的bin目录。

最终的文件目录结构,如下:

.grunt-init/
.grunt-init/gruntfile/
.grunt-init/gruntfile/README.md
.grunt-init/gruntfile/template.js
.grunt-init/gruntfile/root/

然后进入一个准备开发项目的空目录,按Shift+鼠标右键,打开右键菜单,选择“在此处打开命令窗口(W)”,打开命令行,执行如下程序:

grunt-init gruntfile

请注意,此模板将在当前目录中生成文件,如果你不想覆盖现有文件,一定要使用一个新的空目录。

参考资料

使用grunt-run-grunt插件组织团队的开发

项目构建的需求

在组织项目的开发工作时,如果构建工具选型是Grunt,那么常常有以下几个需求:

  1. Gruntfile文件随着项目复杂性的增加,会变的越来越庞大。为了便于开发团队的分工协作,需要在某种程度上分拆Gruntfile,让不同的团队、不同的开发者关注不同的部分。
  2. 把Gruntfile分拆成不同的文件后,既要有统一的入口执行整体的构建,又要能够使分拆后的Gruntfile不依赖于其他部分独立构建,以确保团队分工的互不干扰。
  3. 在整个项目中,package.json和node_modules要能够共用。确保每一部分的开发者,只关注Gruntfile的编写,不需要关注全局共性的package.json和node_modules。

不同的团队、 不同的项目因为细化需求的不同,都会各自选择不同的解决方案。

Gruntfile的分拆方案

Gruntfile文件内的逻辑分组

使用任务的目标名称,在逻辑上分组构建任务。这是大多数项目最初会用到的方法,如grunt中文文档项目(https://github.com/basestyle/grunt-cn)

在传递给grunt.initConfig方法的对象中,使用如下类似的不同任务目标名称区分webapp和pc两类任务:

jshint:{
    webapp: ['app/www/js/main.js'],
    pc: ['js/chart.js','js/common.js','js/demand.js','js/dialog.js','js/formbeautify.js','js/jquery.autopagination.js',
        'js/jquery.cascadeselect.js','js/jquery.datepicker.js','js/jquery.formvalid.js','js/jquery.memberinput.js',
        'js/jquery.multiupload.js','js/jquery.tabs.js','js/pmstation.js','js/project.js','js/setting.js','js/tasktable.js','js/work.js']
},

在自定义的别名任务中,把两类任务分组成webapp和pc两个单任务:

grunt.registerTask('webapp', ['htmlhint:webapp','jshint:webapp','uglify:webapp','cssmin:webapp','copy:webapp','replace:webapp']);
grunt.registerTask('pc', ['jshint:pc','uglify:pc','cssmin:pc','copy:pc','replace:pc']);

如果两类任务调用的任务都相同,也可以使用动态别名任务的方法来处理。

代码示例如下:

grunt.registerTask('build', 'Run all my build tasks.', function(n) {
  if (n == null) {
    grunt.warn('Build num must be specified, like build:001.');
  }
  grunt.task.run('foo:' + n, 'bar:' + n, 'baz:' + n);
});

参见Grunt文档的《常见问题-“动态”别名任务》(中文/英文)。

Grunt配置编程处理在不同的文件中

将配置对象分拆出来,然后在Gruntfile.js中require进来,再将这些不同任务的对象组合成一个对象传递给grunt.initConfig。

上述方案是在我提出问题(有没有分拆Gruntfile.js的方案?(https://github.com/basestyle/grunt-cn/issues/34))后,TooBug[GitHub]和Vincent Hou给出的解决方案。

因为没有进一步展开,所以没有示例代码。

使用自定义任务把构建任务分解在不同的文件中

我在知乎上提出问题《有什么方案可以把较为庞大的gruntfile分拆?(http://www.zhihu.com/question/21766711)》后,墨磊[GitHub]给出的解决方案,基本上就是使用自定义任务把构建任务分解在不同的文件中的。

比如,在gruntjs.com(https://github.com/gruntjs/gruntjs.com)项目中,也是通过把blog、docs等任务从Gruntfile中分拆出来的。

代码示例如下:

// Load local tasks
grunt.loadTasks('tasks'); // getWiki, docs tasks

grunt.registerTask('build', ['clean', 'copy', 'jade', 'docs', 'blog', 'plugins', 'concat']);

上例中的docs、blog任务就是放在tasks目录下,使用grunt.loadTasks方法统一加载的。

选择grunt-run-grunt插件

前文的所述的几个方案,用于分拆Gruntfile虽然很不错,但是并不能解决最初提出的后两个需求:

  • 分拆后的Gruntfile,能够不依赖于其他部分的独立构建。
  • package.json和node_modules要能够共用。

我发现grunt-run-grunt(https://github.com/Bartvds/grunt-run-grunt)插件可以同时满足这三个需求。

很遗憾的是,这个插件关注的人不多,Fork的人也不多。或许其他的开发团队有更高明的方案也说不一定,还需要继续跟进。

项目的组织方案

在选型定案使用grunt-run-grunt插件以后,项目的组织就确定了。

入口Gruntfile的代码示例:

run_grunt:{
  options: {
    'base': require('path').resolve('.')
  },
  main:{
    src: ['src/pro/a.Gruntfile.js', 'src/frameworks/b.lib.Gruntfile.js', 'src/frameworks/c.lib.Gruntfile.js']        
  }
}

需要特别注意的是,options中的base配置是指整个项目所有的Gruntfile都是以这个入口Gruntfile所在的目录为base目录的。

这个扩展机制可以在options中配置最终传递给grunt的命令行参数,属性名是命令行参数名,属性值是命令行参数的值。

上例配置的意义是要求执行类似如下样式的Grunt命令:

grunt --base e:/dev/

各部分的Gruntfile与正常的Gruntfile没有什么区别。唯一的区别就是,这个方案把所有的Gruntfile的base目录设定为入口Gruntfile所在的目录。

比如,src/pro/a.Gruntfile.js中的代码示例如下:

concat: {
  "pro: {
    src: [
      "src/frameworks/b.lib.js",
      "src/frameworks/c.lib.js",
      "src/pro/a.js"
    ],
    dest: "dist/pro/a.js"
  }
}

示例中,不管是src目录,还是dest目录,都是以入口Gruntfile目录为基准的。

当然,base目录不这样安排是最简单的,在入口Gruntfile的run_grunt任务中不配置base就可以了。如此以来,所有的Gruntfile中的目录都是以本Gruntfile所在目录为base目录的。

在javascript项目中使用QUint进行单元测试

QUnit的简介

本文只介绍Qunit的使用,不再介绍单元测试框架的选型过程。

QUnit的官网介绍:

QUnit is a powerful, easy-to-use JavaScript unit testing framework.

QUnit是一个强大的、简单易用的javascript单元测试框架。

下载QUnit

可以到QUnit官网(http://qunitjs.com)上下载qunit.js和qunit.css两个文件。

虽然有jQuery CDN(http://code.jquery.com/)GitHub托管(https://github.com/jquery/qunit)的代码可用,但是为了稳定,还是下载了使用吧。

QUnit上手应用

以下的代码示例直接使用QUnit官网首页的示例。

QUnit单元测试的HTML页面代码示例:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="/resources/qunit.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="/resources/qunit.js"></script>
  <script src="/resources/tests.js"></script>
</body>
</html>

tests.js中的示例代码:

test( "hello test", function() {
  ok( 1 == "1", "Passed!" );
});

QUnit的常用API

断言是单元测试中最常用、最核心的概念,就是在测试的时候,对代码的运行结果作出的一些假设。如果断言成立,就说单元测试通过,反之则视为测试失败。

测试方法和模块

最常用的测试代码,示例如下:

test("该测试用例的主题", function(){/*- 具体的测试断言 -*/});

当测试用例较多时,可以用module()方法进行分组。示例如下:

module( "测试分组一" );
test( "测试方法1", function() {});
test( "测试方法2", function() {});
 
module( "测试分组二" );
test( "测试方法3", function() {});
test( "测试方法4", function() {});

在测试结果HTML页面上,会以 测试分组一:测试方法1 的形式呈现。

异步测试

javascript编程中,经常用到回调函数,所以异步测试也是经常用到的。

如下示例:

asyncTest( "asynchronous test: one second later!", function() {
  expect( 1 );
 
  setTimeout(function() {
    ok( true, "Passed and ready to resume!" );
    start();
  }, 1000);
});

注意start()函数的用法。

由于是异步调用,所以使用expect()告诉QUnit期待几个断言是个好习惯。

QUnit支持的断言

一个测试方法只有一个断言是个好习惯。只是好习惯哦!

一目了然,ok和equal是最常用的

  • ok( state, message ) : 真假断言,state为true则通过。
  • equal( actual, expected, message ) : 相等断言,actual和expected相等(==)则通过。
  • notEqual( actual, expected, message ) : 不等断言,actual和expected不相等(!=)则通过。
  • deepEqual( actual, expected, message ) : 递归相等断言,actual和expected全相等(包括其子元素都相等,适用于基本类型,数组和对象)则通过。
  • notDeepEqual( actual, expected, message ) : 递归不相等断言,actual和expected不全相等(包括其子元素都相等,适用于基本类型,数组和对象)则通过。
  • strictEqual( actual, expected, message ) : 全相等断言,actual和expected全相等(===)则通过。
  • notStrictEqual( actual, expected, message ) : 不全相等断言,actual和expected不全相等(===)则通过。
  • raises( block, expected, message ) : 异常断言,block中抛出异常则通过,expected为可选参数,是所期待抛出异常名的正则表达式。

与Grunt构建工具的集成

Grunt构建工具的安装和使用,可以参考教程《Grunt的安装和使用》

QUnit的Grunt任务

QUnit Grunt任务(grunt-contrib-qunit)的使用,请参见GitHub上的说明https://github.com/gruntjs/grunt-contrib-qunit

使用QUint作单元测试,PhantomJS是必须要安装的。PhantomJS(http://phantomjs.org/) 是一个无界面的WebKit浏览器引擎,还有配套的JavaScript API。它原生支持各种web标准技术:DOM处理,CSS选择器,JSON,Canvas,以及SVG。

安装grunt-contrib-qunit任务时,作为npm依赖项,PhantomJS会自己被安装。

也可以在命令行中使用以下的命令进行安装:

npm install phantomjs -g

友情提示:

本来这一切都是顺理成章,非常流畅的。可惜,一道国墙给这个世界的互联互通制造了些许障碍。

如果电脑上从来没有安装过PhantomJS,或者当前的版本不够高,安装程序会自动从bitbucket网站上下载,例如:http://cdn.bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-windows.zip。但是,非常不幸,这个地址是被墙了的。

你可以从各种不同的途径,获取这个压缩包,然后拷贝到C:\Users\Administrator\AppData\Local\Temp\phantomjs(具体地址根据自己的电脑作调整,或者查看安装报错的信息)。接着,重新启动安装,一切都OK了!

参考资料

佛经

我念一段佛经

我念一段佛经,

祈祷人心的光明。

授你无相的戒律,

约束你的怨嗔。

摩挲你的头顶,

消融你的仇恨。

我念一段佛经,

超度不能转世的灵魂。

阴暗的井底,

冰冷的井水,

一样都是地狱的幽禁。

彻骨的惩罚,

不过是洗手的金盆。

天地轮回中,

从来没有不能原谅的人。

我念一段佛经,

张扬无边的悲悯。

槐树

路边的槐树

2013-07-31

老树沧桑洗礼后,

更比往日枝叶稠。

槐花满地残香殒,

曾见昨夜风雨骤。

十周年

2011-05-03

十年风雨十年路,

一地槐花一地香。

也爱桃花风中舞,

可恨行色太匆忙。