grunt
grunt是一套前端自动化工具,一个基于nodeJs的命令行工具。
安装NodeJS环境
nodeJS官网下载一键式傻瓜安装包:
https://nodejs.org/
安装成功后,打开cmd输入node,如果提示node不是系统内部命令,请将node加入到系统环境变中。现在让我们开始grunt学习之旅吧!
安装gurnt-cli
npm install -g grunt-cli
上述命令执行完后,grunt
命令就被加入到你的系统路径中了,以后就可以在任何目录下执行此命令了。
注意,安装grunt-cli
并不等于安装了 Grunt!Grunt CLI的任务很简单:调用与Gruntfile
在同一目录中 Grunt。这样带来的好处是,允许你在同一个系统上同时安装多个版本的 Grunt。
这样就能让多个版本的 Grunt 同时安装在同一台机器上。
准备npm配置文件(package.json)和grunt配置文件(gruntfile)
package.json: 此文件被npm用于存储项目的元数据,以便将此项目发布为npm模块。你可以在此文件中列出项目依赖的grunt和Grunt插件,放置于devDependencies配置段内。
Gruntfile: 此文件被命名为 Gruntfile.js
或 Gruntfile.coffee
,用来配置或定义任务(task)并加载Grunt插件的。
package.json
package.json
应当放置于项目的根目录中,与Gruntfile
在同一目录中,并且应该与项目的源代码一起被提交。
下面列出了几种为你的项目创建package.json
文件的方式:
{ "name": "my-project-name", "version": "0.1.0", "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-nodeunit": "~0.4.1", "grunt-contrib-uglify": "~0.5.0" } }
安装Grunt 和 grunt插件
向已经存在的package.json
文件中添加Grunt和grunt插件的最简单方式是通过
npm install <module> --save-dev
命令。此命令不光安装了<module>
,还会自动将其添加到devDependencies 配置段中,遵循tilde version range格式。
例如,下面这条命令将安装Grunt最新版本到项目目录中,并将其添加到devDependencies内:
npm install grunt --save-dev
同样,grunt插件和其它node模块都可以按相同的方式安装。下面展示的实例就是安装 JSHint 任务模块:
npm install grunt-contrib-jshint --save-dev
在 Grunt 插件 页面可以看到当前可用的 Grunt 插件,他们可以直接在项目中安装并使用。
安装插件之后,请务必确保将更新之后的 package.json
文件提交到项目仓库中。
Gruntfile
Gruntfile.js
或 Gruntfile.coffee
文件是有效的 JavaScript 或 CoffeeScript 文件,应当放在你的项目根目录中,和package.json
文件在同一目录层级,并和项目源码一起加入源码管理器。
Gruntfile由以下几部分构成:
- "wrapper" 函数
- 项目与任务配置
- 加载grunt插件和任务
- 自定义任务
Gruntfile文件案例
在下面列出的这个 Gruntfile
中,package.json
文件中的项目元数据(metadata)被导入到 Grunt 配置中, grunt-contrib-uglify 插件中的uglify
任务(task)被配置为压缩(minify)源码文件并依据上述元数据动态生成一个文件头注释。当在命令行中执行 grunt
命令时,uglify
任务将被默认执行。
module.exports = function(grunt) {//"wrapper"函数,每一份 Gruntfile
(和grunt插件)都遵循同样的格式,你所书写的Grunt代码必须放在此函数内
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),//将存储在package.json
文件中的JSON元数据引入到grunt config中。
//由于<% %>
模板字符串可以引用任意的配置属性,因此可以通过这种方式来指定诸如文件路径和文件列表类型的配置数据,从而减少一些重复的工作。
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
},
dist:{
src: 'src/<%= pkg.name %>.js',
dest: 'dest/<%= pkg.name %>.min.js'
}
}
});
// 加载包含 "uglify" 任务的插件。
grunt.loadNpmTasks('grunt-contrib-uglify');
// 配置任务,默认被执行的任务列表。
grunt.registerTask('default', ['uglify']);//执行 grunt
命令时如果不指定一个任务的话,将会执行uglify
任务。这和执行grunt uglify
或者 grunt default
的效果一样。default
任务列表数组中可以指定任意数目的任务(可以带参数)
};
这里的initconfig任务配置被分为 Task、Target、options。
Task:就是上文的uglify(压缩插件)。
注:
-
Task配置主要是以任务名称命名的属性,也可以包含其他任意数据。一旦这些代表任意数据的属性与任务所需要的属性相冲突,就将被忽略。eg:
grunt.initConfig({ concat: { // 这里是concat任务的配置信息。 }, uglify: { // 这里是uglify任务的配置信息 }, // 任意数据。 my_property: 'whatever', my_src_files: ['foo/*.js', 'bar/*.js'], });
Target: 就是上文的bulid,dest。
注:
- 这里的bulid,dest可以随便取名字。
- 如果调用uglify的Tasks不指定target,则两个target一起执行。如果要指定需要grunt uglify:bulid的方式。
options:就是上文的options.这里是插件的配置项。
注:
- 在一个任务配置中,
options
属性可以用来指定覆盖内置属性的默认值。此外,每一个目标(target)中还可以拥有一个专门针对此目标(target)的options
属性。目标(target)级的平options将会覆盖任务级的options。options
对象是可选的,如果不需要,可以忽略。eg:
grunt.initConfig({ concat: { options: { // 这里是任务级的Options,覆盖默认值 }, foo: { options: { // "foo" target options may go here, overriding task-level options. }, }, bar: { // No options specified; this target will use task-level options. }, }, });
自定义任务
通过定义 default
任务,可以让Grunt默认执行一个或多个任务。在下面的这个案例中,执行 grunt
命令时如果不指定一个任务的话,将会执行uglify
任务。这和执行grunt uglify
或者 grunt default
的效果一样。default
任务列表数组中可以指定任意数目的任务(可以带参数)。
// Default task(s). grunt.registerTask('default', ['uglify']);
如果Grunt插件中的任务(task)不能满足你的项目需求,你还可以在Gruntfile
中自定义任务(task)。例如,在下面的 Gruntfile
中自定义了一个default
任务,并且他甚至不依赖任务配置:
module.exports = function(grunt) { // A very basic default task. grunt.registerTask('default', 'Log some stuff.', function() { grunt.log.write('Logging some stuff...').ok(); }); };
特定于项目的任务不必在 Gruntfile
中定义。他们可以定义在外部.js
文件中,并通过grunt.loadTasks方法加载。
配置中文件的几种书写形式
由于大多的任务都是执行文件操作,Grunt有一个强大的抽象层用于声明任务应该操作哪些文件。这里有好几种定义src-dest(源文件-目标文件)文件映射的方式,均提供了不同程度的描述和控制操作方式。任何一种多任务(multi-task)都能理解下面的格式,所以你只需要选择满足你需求的格式就行。
所有的文件格式都支持src
和dest
属性,此外"Compact"[简洁]和"Files Array"[文件数组]格式还支持以下一些额外的属性:
filter
它通过接受任意一个有效的fs.Stats方法名或者一个函数来匹配src
文件路径并根据匹配结果返回true
或者false
。nonull
如果被设置为true
,未匹配的模式也将执行。结合Grunt的--verbore
标志, 这个选项可以帮助用来调试文件路径的问题。dot
它允许模式模式匹配句点开头的文件名,即使模式并不明确文件名开头部分是否有句点。matchBase
如果设置这个属性,缺少斜线的模式(意味着模式中不能使用斜线进行文件路径的匹配)将不会匹配包含在斜线中的文件名。 例如,a?b将匹配/xyz/123/acb
但不匹配/xyz/acb/123
。expand
处理动态的src-dest
文件映射,更多的信息请查看动态构建文件对象。- 其他的属性将作为匹配项传递给底层的库。 请查看node-glob 和minimatch 文档以获取更多信息。
简洁格式
这种形式允许每个目标对应一个src-dest文件映射。通常情况下它用于只读任务,比如grunt-contrib-jshint,它就只需要一个单一的src
属性,而不需要关联的dest
选项. 这种格式还支给每个src-dest
文件映射指定额外的属性。
grunt.initConfig({ jshint: { foo: { src: ['src/aa.js', 'src/aaa.js'] }, }, concat: { bar: { src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b.js', }, }, });
文件对象格式
这种形式支持每个目标对应多个src-dest
形式的文件映射,属性名就是目标文件,源文件就是它的值(源文件列表则使用数组格式声明)。可以使用这种方式指定数个src-dest
文件映射, 但是不能够给每个映射指定附加的属性。
grunt.initConfig({ concat: { foo: { files: { 'dest/a.js': ['src/aa.js', 'src/aaa.js'], 'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'], }, }, bar: { files: { 'dest/b.js': ['src/bb.js', 'src/bbb.js'], 'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'], }, }, }, });
文件数组格式
这种形式支持每个目标对应多个src-dest
文件映射,同时也允许每个映射拥有额外属性:
grunt.initConfig({ concat: { foo: { files: [ {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'}, {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'}, ], }, bar: { files: [ {src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b/', nonull: true}, {src: ['src/bb1.js', 'src/bbb1.js'], dest: 'dest/b1/', filter: 'isFile'}, ], }, }, });
自定义过滤函数
filter
属性可以给你的目标文件提供一个更高级的详细帮助信息。只需要使用一个有效的fs.Stats 方法名。下面的配置仅仅清理一个与模式匹配的真实的文件:
grunt.initConfig({ clean: { foo: { src: ['tmp/**/*'], filter: 'isFile', }, }, });
或者创建你自己的filter
函数,根据文件是否匹配来返回true
或者false
。下面的例子将仅仅清理一个空目录:
grunt.initConfig({ clean: { foo: { src: ['tmp/**/*'], filter: function(filepath) { return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0); }, }, }, });
通配符模式
通常分别指定所有源文件路径是不切实际的,因此Grunt通过内置支持node-glob 和 minimatch 库来匹配文件名(又叫作globbing
)。
然这并不是一个综合的匹配模式方面的教程,你只需要知道如何在文件路径匹配过程中使用它们即可:
*
匹配任意数量的字符,但不匹配/
?
匹配单个字符,但不匹配/
**
匹配任意数量的字符,包括/
,只要它是路径中唯一的一部分{}
允许使用一个逗号分割的“或”表达式列表!
在模式的开头用于排除一个匹配模式所匹配的任何文件
每个人都需要知道的是:foo/*.js
将匹配位于foo/
目录下的所有的.js
结尾的文件;而foo/**/*js
将匹配foo/
目录以及其子目录中所有以.js
结尾的文件。
此外, 为了简化原本复杂的通配符模式,Grunt允许指定一个数组形式的文件路径或者一个通配符模式。所有模式按顺序处理,模式处理的过程中,带有!
前缀的模式所匹配的文件将不包含在结果集中。 而且其结果集中的每一项也是唯一的。
例如:
// 指定单个文件: {src: 'foo/this.js', dest: ...} // 指定一个文件数组: {src: ['foo/this.js', 'foo/that.js', 'foo/the-other.js'], dest: ...} // 使用一个匹配模式: {src: 'foo/th*.js', dest: ...} // 一个独立的node-glob模式: {src: 'foo/{a,b}*.js', dest: ...} // 也可以这样编写: {src: ['foo/a*.js', 'foo/b*.js'], dest: ...} // foo目录中所有的.js文件,按字母顺序排序: {src: ['foo/*.js'], dest: ...} // 首先是bar.js,接着是剩下的.js文件,并按字母顺序排序: {src: ['foo/bar.js', 'foo/*.js'], dest: ...} // 除bar.js之外的所有的.js文件,按字母顺序排序: {src: ['foo/*.js', '!foo/bar.js'], dest: ...} // 按字母顺序排序的所有.js文件,但是bar.js在最后。 {src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...} // 模板也可以用于文件路径或者匹配模式中: {src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'} // 它们也可以引用在配置中定义的其他文件列表: {src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}
更多关于通配符模式的语法,请查看node-glob 和 minimatch 的文档。
动态构建文件对象
当你希望处理大量的单个文件时,这里有一些附加的属性可以用来动态的构建一个文件列表。这些属性都可以用于Compact
和Files Array
文件映射格式。
expand
设置为true
用于启用下面的选项:
cwd
所有src
指定的匹配都将相对于此处指定的路径(但不包括此路径) 。src
相对于cwd
路径的匹配模式。dest
目标文件路径前缀。ext
对于生成的dest
路径中所有实际存在文件,均使用这个属性值替换扩展名。extDot
用于指定标记扩展名的英文点号的所在位置。可以赋值'first'
(扩展名从文件名中的第一个英文点号开始) 或'last'
(扩展名从最后一个英文点号开始),默认值为'first'
[添加于 0.4.3 版本]flatten
从生成的dest
路径中移除所有的路径部分。rename
对每个匹配的src
文件调用这个函数(在重命名后缀和移除路径之后)。dest
和匹配的src
路径将被作为参数传入,此函数应该返回一个新的dest
值。 如果相同的dest
返回不止一次,那么,每个返回此值的src
来源都将被添加到一个数组中作为源列表。
在下面的例子中,uglify
任务中的static_mappings
和dynamic_mappings
两个目标具有相同的src-dest
文件映射列表, 这是因为任务运行时Grunt会自动展开dynamic_mappings
文件对象为4个单独的静态src-dest
文件映射--假设这4个文件能够找到。
可以指定任意静态src-dest
和动态的src-dest
文件映射相互结合。
grunt.initConfig({ uglify: { static_mappings: { // Because these src-dest file mappings are manually specified, every // time a new file is added or removed, the Gruntfile has to be updated. files: [ {src: 'lib/a.js', dest: 'build/a.min.js'}, {src: 'lib/b.js', dest: 'build/b.min.js'}, {src: 'lib/subdir/c.js', dest: 'build/subdir/c.min.js'}, {src: 'lib/subdir/d.js', dest: 'build/subdir/d.min.js'}, ], }, dynamic_mappings: { // Grunt will search for "**/*.js" under "lib/" when the "uglify" task // runs and build the appropriate src-dest file mappings then, so you // don't need to update the Gruntfile when files are added or removed. files: [ { expand: true, // Enable dynamic expansion. cwd: 'lib/', // Src matches are relative to this path. src: ['**/*.js'], // Actual pattern(s) to match. dest: 'build/', // Destination path prefix. ext: '.min.js', // Dest filepaths will have this extension. extDot: 'first' // Extensions in filenames begin after the first dot }, ], }, }, });
模板
使用<% %>
分隔符指定的模板会在任务从它们的配置中读取相应的数据时将自动扩展扫描。模板会被递归的展开,直到配置中不再存在遗留的模板相关的信息(与模板匹配的)。
整个配置对象决定了属性上下文(模板中的属性)。此外,在模板中使用grunt
以及它的方法都是有效的,例如: <%= grunt.template.today('yyyy-mm-dd') %>
。
<%= prop.subprop %>
将会自动展开配置信息中的prop.subprop
的值,不管是什么类型。像这样的模板不仅可以用来引用字符串值,还可以引用数组或者其他对象类型的值。<% %>
执行任意内联的JavaScript代码。对于控制流或者循环来说是非常有用的。
下面以concat
任务配置为例,运行grunt concat:sample
时将通过banner中的/* abcde */
连同foo/*.js
+bar/*.js
+bar/*.js
匹配的所有文件来生成一个名为build/abcde.js
的文件。
grunt.initConfig({ concat: { sample: { options: { banner: '/* <%= baz %> */\n', // '/* abcde */\n' }, src: ['<%= qux %>', 'baz/*.js'], // [['foo/*.js', 'bar/*.js'], 'baz/*.js'] dest: 'build/<%= baz %>.js', // 'build/abcde.js' }, }, //用于任务配置模板的任意属性 foo: 'c', bar: 'b<%= foo %>d', // 'bcd' baz: 'a<%= bar %>e', // 'abcde' qux: ['foo/*.js', 'bar/*.js'], });