网站的建立让我逐步学习到以前从未接触过的技术,这里就简单谈谈网站构建时Grunt自动化工具的使用心得。随着网站的开发进行,慢慢认识到Grunt就是一个能在开发中精简流程、提高效率、减少错误率的自动化工具。它能有效的简化庞大复杂系统的维护、打包、发布等流程,从而节省时间。
Grunt一个基于Node.js的命令行的前端 Javascript 自动化构建工具。是帮助开发者完成大部分重复性工作的有效工具。例如:
- 压缩文件
- 合并文件
- 简单语法检查
npm是 Node.js 的包管理工具,而Grunt和grunt插件 是基于npm 安装并管理的。Grunt 0.4.x 版本必须配合Node.js 0.8.0以上的版本使用。
安装CLI
因为 grunt 是基于 Node.js 的,所以我们需要安装 Node.js 环境,在 WebStorm 的 Terminal 命令行中运行 npm install -g grunt-cli 将 Grunt 命令行安装到全局环境中。此时, Grunt 命令就被加入到系统路径中了,以后就可以在任何目录下执行此命令了。
Grunt CLI 的任务是调用与 Gruntfile 在同一目录中 Grunt,所以安装了 grunt-cli 并不等于安装了 Grunt 。网站中运行 grunt 时它会利用 node 提供的 require() 系统查找本地安装的 Grunt。所以可以在项目的任意子目录中运行grunt。如果找到本地安装的 Grunt, CLI 就将其加载,并传递 Gruntfile 中的配置信息,然后执行所指定的任务。图为网站中 Gruntfile.js 文件。

图 1
安装Grunt
首先如图1需要在项目中添加两份文件:package.json 和 Gruntfile.js。
1.package.json 文件
package.json文件作用
package.json文件被npm用于存储项目的元数据,以便将网站发布为npm模块。这个文件用来存储npm模块的依赖项。文件中列出了项目依赖的grunt和Grunt插件,放置于devDependencies配置段内。下面为网站中Grunt和grunt插件配置信息:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18"devDependencies": {
"debug": "~0.7.4",
"grunt": "~0.4.2",
"grunt-cli": "~0.1.13",
"grunt-concurrent": "~0.5.0",
"grunt-contrib-htmlmin": "~0.2.0",
"grunt-contrib-imagemin": "^0.8.1",
"grunt-contrib-jshint": "~0.9.2",
"grunt-contrib-watch": "~0.5.3",
"grunt-env": "~0.4.1",
"grunt-jsbeautifier": "^0.2.7",
"grunt-lesslint": "~1.1.7",
"grunt-mocha-cov": "~0.2.1",
"grunt-nodemon": "~0.2.0",
"jshint-stylish": "~0.1.5",
"open": "~0.0.5",
"should": "~3.1.3"
}package.json文件的位置及创建方式
package.json应当放置于项目的根目录中,与Gruntfile在同一目录中,并且应该与项目的源代码一起被提交。在图1package.json所在目录中运行npm install将依据package.json文件中所列出的每个依赖来自动安装适当版本的依赖。- 项目创建
package.json文件的方式一般有如下三种:- grunt-init 模版都会自动创建特定于项目的
package.json文件; - npm init命令会创建一个基本的
package.json文件; - 复制下面的案例,并根据需要做扩充:
1
2
3
4
5
6
7
8
9{
"name": "my-project-name",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-nodeunit": "~0.2.0",
"grunt-contrib-uglify": "~0.2.2"
}
- grunt-init 模版都会自动创建特定于项目的
- 项目创建
package.json文件中各字段含义以下为网站
package.json文件的配置内容:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26{
"name": "eRealm",
"description": "Home page for eReaml.",
"version": "1.0.0",
"keywords": [
"open source",
"node.js",
"home page"
],
"homepage": "http://www.erealm.cn",
"author": "dangjian",
"repository": {
"type": "git",
"url": "https://github.com/eRealm/HomeSite.git"
},
"config": {
"unsafe-perm": true
},
"bugs": {
"url": "https://github.com/eRealm/HomeSite/issues",
"email": "ken@ereaml.com.my"
},
"license": "",
"dependencies": {"express": "^3.4.8"···},
"devDependencies": {"debug": "~0.7.4"···}
}name和version字段是必须的,它们一起组成的标识是唯一的。如果没有就无法安装。Description:网站简介,字符串;Keywords:关键字,数组、字符串;Homepage:网站的url;Bugs:网站项目提交问题的url和邮件地址,对解决问题很有帮助;License:指定一个许可证,让人知道使用和限制的权利;Author:网站开发者;Repository:本网站代码存放的地方,这个对希望贡献的人有帮助;Config:可以用来配置用于包脚本中的跨版本参数;Dependencies:依赖是给一组包名指定版本范围的一个hash,这个版本范围是一个由一个或多个空格分隔的字符串;devDependencies:别人在程序中下载并使用我们的模块时,不用去下载并构建网站使用的外部测试或者文档框架;
安装grunt插件
通过以下命令向已经存在的
package.json文件中添加Grunt和grunt插件。此命令不光安装了module,还会自动将其添加到devDependencies配置段中。
npm install <module> --save-dev所以网站中,再运用下面这条命令将安装
Grunt最新版本到项目目录中,并将其添加到devDependencies内:
npm install grunt --save-dev同样,
grunt插件和其它node模块都可以按相同的方式安装。然后在根目录下执行npm install将相关的文件下载下来:![图2]()
图 2Gruntfile.js 文件
Gruntfile.js文件位置及作用
Gruntfile.js文件是有效的JavaScript文件,放在项网站根目录中,和package.json文件在同一目录层级,并和项目源码一起加入源码管理器。Gruntfile.js文件用于读取package信息、插件加载、注册任务和运行任务。Gruntfile.js由以下几部分构成:"wrapper"函数- 网站
Gruntfile文件的wrapper部分的函数如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17module.exports = function(grunt){
var pkg = grunt.file.readJSON('package.json');
grunt.initConfig({
pkg: pkg,
asset: 'public/asset-'+ pkg.version,
bower: {
install: {
targetDir: "public/vendor",
install: true,
verbose: true,
cleanTargetDir: true,
cleanBowerDir: false,
bowerOptions: {}
}
}
})
- 网站
- 每一份
Gruntfile和grunt插件都遵循同样的格式,所有的Grunt代码必须放在此函数内。 - 项目的元数据是从
package.json文件中导入到Grunt配置中的,grunt.file.readJSON方法用于引入JSON数据。grunt-contrib-uglify插件中的uglify任务被配置用于压缩一个源文件以及使用该元数据动态的生成一个banner注释。见以下代码。
项目与任务配置
如以上
wrapper函数,Grunt任务都依赖某些配置数据,这些数据被定义在一个object内,并传递给grunt.initConfig方法。grunt.file.readJSON('package.json')将存储在package.json文件中的JSON元数据引入到grunt config中。由于Gruntfile.js是javascript文件,所以配置信息不只是JSON格式,这里也可以使用有效的js代码。以下为
package.json文件中的grunt-contrib-uglify插件中的uglify任务要求它的配置被指定在一个同名属性中。网站使用uglify任务的build的目标,用于将多个public目录下的js文件压缩为一个目标文件,即libs.min.js和app.min.js文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33uglify: {
options:{
mangle: true,
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version + "\\n" %>' +
'* <%= grunt.template.today("yyyy-mm-dd HH:MM:ss") + "\\n" %>' +
'* <%= pkg.homepage + "\\n" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> - <%= pkg.author %> */ <%= "\\n" %>'
},
build: {
files: {
'public/javascripts/libs.min.js': [
'public/javascripts/libs/*.js',
'public/javascripts/libs/plugins/jquery.wookmark.min.js',
'public/javascripts/libs/plugins/imagesloaded.pkgd.min.js',
'public/javascripts/libs/plugins/angular-cookies.min.js',
'public/javascripts/libs/plugins/angular-translate.min.js',
'public/javascripts/libs/plugins/angular-translate-loader-url.min.js',
'public/javascripts/libs/plugins/angular-translate-storage-cookie.min.js',
'public/javascripts/libs/plugins/ui-bootstrap-tpls.min.js',
'public/javascripts/libs/plugins/ng-mobile-menu.min.js',
'public/javascripts/libs/plugins/bootstrap.min.js',
'public/javascripts/libs/plugins/moment-with-locales.js'
] ,
'public/javascripts/app.min.js': [
'public/javascripts/erealm.js',
'public/javascripts/language.js',
'public/javascripts/clients.js',
'public/javascripts/app/*.js'
]
}
}
}上面代码中
banner中<% %>分隔符指定的模板会在任务从它们的配置中读取相应的数据时将自动扩展扫描。模板会被递归的展开,直到配置中不再存在遗留的模板相关的信息。运行grunt时,uglify将通过banner中的pkg.title、pkg.name、pkg.version和pkg.homepage等匹配加载进来的package.json中所有数据来生成文件注释。当运行一个任务时,
Grunt会自动查找配置对象中的同名属性。多任务可以通过任意命名的目标来定义多个配置。如以下代码中的jshint任务有名为client和sever两个目标,同时指定任务和目标,例如grunt jshint:client或者grunt jshint:sever,将只会处理指定目标的配置,而运行grunt jshint将遍历所有目标并依次处理。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25jshint: {
options: {
reporter: require('jshint-stylish')
},
client: {
options: {
jshintrc: '.jshintrc-client',
ignores: [
'public/javascripts/`/*.min.js'
]
},
src: [
'public/javascripts/`/*.js'
]
},
server: {
options: {
jshintrc: '.jshintrc-server'
},
src: [
'config/`/*.js',
'app/`/*.js'
]
}
}在一个任务配置中,
options属性可以用来指定覆盖内置属性的默认值。每一个目标中还可以拥有一个专门针对此目标的options属性。目标级的options将会覆盖任务级的options。
加载
grunt插件和任务- 像
grunt-contrib-uglify 、grunt-contrib-copy等这些常用grunt插件被加载了进来。只要在package.json文件中被列为依赖的包,并通过npm install安装之后,都可以在Gruntfile中以简单命令的形式使用。如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.loadNpmTasks('grunt-jsbeautifier');
grunt.loadNpmTasks('grunt-lesslint');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.loadNpmTasks('grunt-filerev');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-usemin');
- 像
网站中常用
grunt插件功能介绍以下为网站中加载的
grunt插件功能简述:grunt-contrib-copy:用于复制文件或目录,复制src中的文件;grunt-contrib-imagemin:优化并压缩网页图片;grunt-contrib-jshint:用于javascript代码检查,并会给出建议,发布js代码前执行jshint任务,可以避免 - 出现一些低级语法问题;grunt-contrib-concat:用于合并任意文件;grunt-contrib-clean:用于删除文件或目录;grunt-contrib-uglify:用于压缩文件;grunt-concurrent:并发执行任务;grunt-contrib-cssmin:用于网站css文件压缩;grunt-contrib-watch:监听files中文件变动,并自动刷新;grunt-usemin:用来替换模板里的链接为更改后的模板文件的样子;grunt-contrib-less:网站用到Bootstrap,此插件功能是将css文件压缩成less文件;grunt-filerev:对静态资源进行文件重命名;grunt-autoprefixer:解析CSS文件并且添加浏览器前缀到CSS规则里;grunt-nodemon:用于实时监听app.js文件,实现运行grunt后自动启动浏览器以3000端口打开网页;
自定义任务
- 代码中通过定义
default任务,可以让Grunt默认执行一个或多个任务。在网站中,执行grunt命令时如果不指定一个任务的话,将会执行lesslint,jshint,less:debug, concurrent,autoprefixer:debug任务。这和执行grunt task或者grunt default的效果一样。default任务列表数组中可以指定任意数目的任务(可以带参数)。1
2
3
4
5
6
7grunt.option('force', true);
grunt.registerTask('prepare', ['copy:main', 'imagemin', 'copy:images', 'clean:images',
'jsbeautifier']);
grunt.registerTask('default', ['lesslint', 'jshint','less:debug','autoprefixer:debug',
"concurrent"]);
grunt.registerTask('build', ['cssmin', 'less:compile','autoprefixer:compile','uglify',
'filerev','usemin', 'copy:build', 'clean:build']);
- 代码中通过定义
文件格式
由于大多的任务都是执行文件操作,
Grunt有一个强大的抽象层用于声明任务应该操作哪些文件。src-dest(源文件-目标文件)文件映射的方式有:- 简洁格式
- 文件对象格式
- 文件数组格式等格式
这些方式均提供了不同程度的描述和控制操作方式。网站采用的是文件数组格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29copy: {
main: {
files: [
{
expand: true, cwd: 'public/vendor/jQuery/dist',
src: ['jquery.min.map','jquery.min.js'], dest: "public/javascripts/libs"
},
{
expand: true, cwd: 'public/vendor/angular',
src: ['angular.min.js.map','angular.min.js'], dest: "public/javascripts/libs"
},
{
expand: true, cwd: 'public/vendor/bootstrap/dist/css',
src: ['bootstrap.min.css'], dest: "public/stylesheets/libs"
},
{
expand: true, cwd: 'public/vendor/angular-bootstrap',
src: ['ui-bootstrap-tpls.min.js'], dest: "public/javascripts/libs/plugins"
},
{
expand: true, cwd: 'public/vendor/font-awesome/fonts',
src: ['`'], dest: "public/stylesheets/fonts"
},
{
expand: true, cwd: 'public/vendor/bootstrap/fonts',
src: ['`'], dest: "public/stylesheets/fonts"
}
]
}这里让
copy任务将所有存在于src/目录下的文件合并起来,然后存储在dist目录中,并以项目名来命名。
相关插件功能配置
当所有需要的 grunt 插件加载进来后:
JSHintJSHint是Javascript代码验证工具,这种工具可以检查代码并提供相关的代码改进意见。JSHint只需要一个需要检测的文件数组,然后是一个options对象,这个对象用于重写JSHint提供的默认检测规则。如以下代码,options对象中修改了JSHint默认检测规则,分别使用.jshintrc-client和.jshintrc-sever插件,检测public/javascript/目录、config/目录和app/目录下的所有.js文件并忽略掉该目录下所有.min.js文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25jshint: {
options: {
reporter: require('jshint-stylish')
},
client: {
options: {
jshintrc: '.jshintrc-client',
ignores: [
'public/javascripts/`/*.min.js'
]
},
src: [
'public/javascripts/`/*.js'
]
},
server: {
options: {
jshintrc: '.jshintrc-server'
},
src: [
'config/`/*.js',
'app/`/*.js'
]
}
}
watch- 当它检测到任何
files里的文件发生变化时,它就会按照顺序执行tasks里相应的任务,即刷新。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24watch: {
clientHtml: {
files: ['public/templates/`/*.html'],
options: {
livereload: true
}
},
clientJS: {
files: [
'public/`/*.js', '!client/app/`/*.min.js'
],
tasks: ['newer:jshint:client'],
options: {
livereload: true
}
},
clientLess: {
files: ['public/stylesheets/`/*.less'],
tasks:['less:debug','autoprefixer:debug'],
options: {
livereload: true
}
}
}
- 当它检测到任何
copycopy任务将public/vendor/jQuery/dist目录下的文件复制到public/javascripts/libs中。1
2
3
4
5
6
7
8
9
10
11
12
13
14copy: {
main: {
files: [
{
expand: true, cwd: 'public/vendor/jQuery/dist',
src: ['jquery.min.map','jquery.min.js'], dest: "public/javascripts/libs"
},
{
expand: true, cwd: 'public/vendor/angular',
src: ['angular.min.js.map','angular.min.js'], dest: "public/javascripts/libs"
}
]
}
}
imageminimagemin任务将public/images目录下的所有.png、.jpg、.gif格式图片优化压缩到public/images-buidl/目录下。1
2
3
4
5
6
7
8
9
10imagemin: {
dynamic: {
files: [{
expand: true,
cwd: 'public/images',
src: ['`/*.{png,jpg,gif}'],
dest: 'public/images-build/'
}]
}
}
lessless任务将app.css文件解析为app.less文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18less: {
debug: {
options: {
cleancss: false
},
files: {
'public/stylesheets/app.css': 'public/stylesheets/app.less'
}
},
compile: {
options: {
cleancss: true
},
files: {
'public/stylesheets/app.min.css': 'public/stylesheets/app.less'
}
}
}
总之 Grunt 就是为了自动化。对于前端为了明确模块,我们可以会将 JavaScript、CSS 等代码拆解成很多个模块,他们都有独立的一个个文件,但是会导致整个项目文件太多,不利于页面优化。通过 Grunt 工具我们将这些文件压缩合并起来,这样我们就能免去很多手动操作,既保证效率又保证质量,高效完成任务。以上只是通过本网站个人初涉 Grunt 的一个心得,如有错误欢迎指出。

