Grunt项目中都是通过“Gruntfile.js”来配置任务。可以说,“Gruntfile.js”文件在任何一个Grunt项目中都是必不可少的,从这也可以看出这个文件的重要性。欲要搞清楚Grunt怎么工作,就必须要先了解Grunt是如何通过“Gruntfile.js”配置任务,帮助你实现你需要的功能。我们通过下面的介绍,学习如何配置。

Gruntfile.js创建


 

假设您已经在您的系统中创建了一个Grunt项目“my-grunt”。要正常的运行这个Grunt项目,其中package.json和Gruntfile.js是必不可少的,而且他们都必须位于Grunt项目的跟目录之下。

最简单的Gruntfile.js文件内容包含如下初始信息:

module.exports = function(grunt){
    //配置项目
    grunt.initConfig({
        //配置任务
    });

    //加载任务
    grunt.loadNpmTasks('grunt任务插件名');

    //默认任务
    grunt.registerTasks('default',['任务名']);
};

 

文件搞定后,那么问题来了,我们应该怎么在这个文件中配置需要的任务。接下来,我们一起来了解他的配置。

Wrapper函数

 


 

每个Gruntfile.js和Grunt插件都是使用这个基本格式,所有你的Grunt代码都必须指定在这个函数里面:

 

module.exports = function(grunt){
//你的grunt代码
};

 

Grunt配置

Grunt的任务配置都是在你的Gruntfile.js文件中的grunt.initConfig({});方法中指定。这个配置主要都是一些命名任务属性,通常任务都被定义为一个对象作为参数给grunt.initConfig()方法,而任务都是作为这个对象的属性定义。也可以包含任意其他数据。但这些属性不能与你的任务所需要的属性相冲突,否则它将被忽略(一般情况下,任务中的属性名都是约定俗成的)

此外,由于这本身就是javascript,因此你不仅限于使用json;你可以在这里使用任何有效的javascript。必要的情况下,你甚至可以以编程的方式生成配置(比如通过其他的程序生成一个或多个任务配置)。

gruntinitCongif({
    concat:{
        //这里是concat任务的配置信息
    },
    uglify:{
        //这里是uglify任务的配置信息
    },
    my_property:'whatever',
    my_src_file:['foo/*js','bar/*js']
});

 

项目的任务配置


 

前面说过,大多数Grunt任务所依赖的配置数据都被定义在传递给grunt.initConfig方法的一个对象中。我们先来看一个简单的例子,为了实现实例中的功能,需要先完善package.json文件的内容,如下所示:

{
  "name": "my-grunt",
  "version": "0.1.0",
  "description": "this is test project with grunt.",
  "author": "airen",
  "license": "BSD",
  "devDependencies": {
        "grunt": "~0.4.1",
        "grunt-contrib-uglify": "*"
    }
}

 

 

如果你不认识package.json中的内容,没关系,在下一节中我们将对package.json文件做一个较为详细的阐述。

在下面的示例中,我们会看到grunt.file.readJSON('pageage.json')会把存储在package.json中的JSON元素数据导入到Grunt配置中。由于<% %>模板字符串可以引用任意的配置属性,因此可以通过这种方式来制定诸如文件路径和文件列表类型的配置数据,从而减少一些重复的工作(比如我们通常需要通过复制粘贴的方式来在不同的地方引用同一个属性,使用<%%>的方式可以简单的理解为将某些特定的数据存储在变量中,然后在其他地方像使用变量一样使用这些数据属性)。

与大多数任务一样,grunt-contrib-uglify插件的uglify任务要求他的配置被指定在同一个同名属性中。在这里有一个例子,我们指定了一个banner选项(用于在文件顶部生成一个注释),紧接都会是一个单一的名为build的uglify目录,用于将一个js文件压缩为一个目标文件(比如将src/js目录下的jquery-1.9.0.js压缩为jquery-1.9.min.js,然后存储到dest/js目录)。

 

// 项目配置
grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
        options: {
            banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
        },
        build: {
            src: 'src/js/<%= pkg.name %>.js',
            dest: 'build/js/<%= pkg.name %>.min.js'
        }
    }
});

任务配置和目标

 


 

当运行一个任务时,Grunt会自动查找配置对象中的同名属性。多个任务可以有多个配置,每个任务可以使用任意的命名“targets”来自定义多个任务目标。在上面的示例中,添加Sass任务:

//Wrapper函数
module.exports = function(grunt) {

  // 配置项目
  grunt.initConfig({
    // 配置任务
    pkg:grunt.file.readJSON('package.json'),

    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'src/js/<%= pkg.name %>.js',
        dest: 'build/js/<%= pkg.name %>.min.js'
      }
    },
    sass: {
      dist: {
        options: {
          style: 'compressed'
        },
        expand: true,
        cwd: 'src/styles/sass/',
        src: ['*.scss'],
        dest: 'build/styles/css',
        ext: '.css'
      },
      dev: {
        options: {
          style: 'expanded',
          debugInfo: true,
          lineNumbers: true
        },
        expand: true,
        cwd: 'src/styles/sass/',
        src: ['*.scss'],
        dest: 'build/styles/css',
        ext: '.css'
      }
    }
  });
};

 

在示例当中,sass任务有两个字任务distdev。在命令行中执行grunt sass:dist或者grunt sass:dev的任务和目标只会处理指定的任务目标配置,而运行grunt sass将遍历所有的(定义在sass任务中的)目标并依次处理。注意,如果一个任务使用grunt.renameTask重命过,Grunt将在配置对象中查找新的任务名称属性。

加载Grunt插件和任务

 


许多常用的任务像concatenation,minification和linting都被作为grunt插件来使用。只要一个插件被作为一个依赖指定在项目的package.json文件中,并且已经通过npm install安装好,都可以在你的Gruntfile.js文件中使用下面这个简单的命令启用它(所依赖的任务)。

//加载提供“uglify”任务的插件 
grunt.loadNpmTasks('grunt-contrib-uglify');
//加载提供"sass"任务插件 
grunt.loadNpmTasks('grunt-contrib-sass');

设置默认任务


Gruntfile.js文件中,你可以通过定义一个default任务来配置Grunt,让它默认运行一个或者多个任务。在下面的例子中,在命令中运行grunt而不指定特定的任务将自动运行uglifysass任务。这个功能与显示的运行grunt uglifygrunt sass或者等价运行grunt default一样。你可以在任务参数数组指定任意数量的任务(这些任务可以带参数,也可以不带参数)。

// 默认任务
grunt.registerTask('default', ['uglify','sass']);

自定义任务


如果你的项目所需的任务没有对应的Grunt插件提供相应的功能,你可以在Grunt内定义自定义的任务。例如,下面的Gruntfile就定义了一个完整的自定义的default任务,它甚至没有利用任务配置(没有使用grunt.initConfig({})方法):

module.exports = function(grunt){
//一个非常基础的default任务
grunt.registerTask('default','Log some stuff.',function(){
grunt.log.write('Logging some stuff...').ok();
});
};

自定义的项目特定的任务可以不定义在Gruntfile.js中;它可以定义在一个外部.js文件中,然后通过grunt.loadTasks方法来加载。

options


在一个任务配置中,options属性可以用来指定覆盖内置属性的默认值。此外,每一个任务目标中更具体的目标都可以拥有一个options属性。目标级的选项将会覆盖任务级的选项(就近原则——options离目标越近,其优先级越高)。

options对象是可选,如果不需要,可以省略。

grunt.initConfig({
concat: {
options: {
// 这里是任务级的Options,覆盖任务的默认值 
},
foo: {
options: {
// 这里是'foo'目标的options,它会覆盖任务级的options.
}
},
bar: {
// 没有指定options,这个目标将使用任务级的options
}
}
});

文件


 

由于大多的任务都是执行文件操作,Grunt有一个强大的抽象声明说明任务应该操作哪些文件。这里有好几种定义src-dest(源文件——目标文件)文件映射的方式,都提供了不同程度的描述和控制操作方式。任何一种多任务(包含多个任务目标的任务)都能理解下面的格式,所以你只需要选择满足你需要的格式就行。

 

所有的文件格式都支持srcdest属性。

简洁格式


这种形式允许每个目标对应一个src-dest文件映射。通常情况下它用于只读任务,比如grunt-contrib-jshint,它就只需要一个单一的src属性,而不需要关联的dest选项。这种格式还支持给每个src-dest文件映射指定附加属性。

文件对象格式

 


这种形式支持每个任务目标对就多个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'}
]
}
}
});

较老的格式

 


dest-as-target文件格式在多任务和目标形式出现之前是一个过渡形式,目标文件路径实际上就是目标名称。遗憾的是, 由于目标名称是文件路径,那么运行grunt task:target可能不合适。此外,你也不能指定一个目标级的options或者给每个src-dest文件映射指定附加属性。

grunt.initConfig({
concat: {
'dest/a.js': ['src/aa.js', 'src/aaa.js'],
'dest/b.js': ['src/bb.js', 'src/bbb.js']
}
});

自定义过滤函数

 


filter属性可以给你的目标文件提供一个更高级的详细帮助信息。只需要使用一个有效的fs.Stats方法名。下面的配置仅仅清理一个与模式匹配的真实的文件:

grunt.initConfig({
clean: {
foo: {
src: ['temp/**/*'],
filter: 'isFile'
}
}
});

或者创建你自己的filter函数根据文件是否匹配来返回true或者false。下面的例子将仅仅清理一个空目录:

grunt.initConfig({
clean: {
foo: {
src: ['temp/**/*'],
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文档中查看更多的关于通配符模式的语法。

构建动态文档对象

 


当你希望处理大量的单个文件时,这里有一些附加的属性可以用来动态的构建一个文件. 这些属性都可以指定在CompactFiles Array映射格式中(这两种格式都可以使用)。

 

  • expand设置true用于启用下面的选项:
  • cwd相对于当前路径所匹配的所有src路径(但不包括当前路径。)
  • src相对于cwd路径的匹配模式。
  • dest目标文件路径前缀。
  • ext使用这个属性值替换生成的dest路径中所有实际存在文件的扩展名(比如我们通常将压缩后的文件命名为.min.js)。
  • flatten从生成的dest路径中移除所有的路径部分。
  • rename对每个匹配的src文件调用这个函数(在执行extflatten之后)。传递dest和匹配的src路径给它,这个函数应该返回一个新的dest值。 如果相同的dest返回不止一次,每个使用它的src来源都将被添加到一个数组中。

在下面的例子中,minify任务将在static_mappingsdynamic_mappings两个目标中查看相同的src-dest文件映射列表, 这是因为任务运行时Grunt会自动展开dynamic_mappings文件对象为4个单独的静态src-dest文件映射--假设这4个文件能够找到。

可以指定任意结合的静态src-dest和动态的src-dest文件映射。

grunt.initConfig({
minify: {
static_mappings: {
//由于这里的src-dest文件映射时手动指定的, 每一次新的文件添加或者删除文件时,Gruntfile都需要更新
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: {
//当'minify'任务运行时Grunt将自动在"lib/"下搜索"**/*.js", 然后构建适当的src-dest文件映射,因此你不需要在文件添加或者移除时更新Gruntfile
files: [
{
expand: true, //启用动态扩展
cwd: 'lib/', //批匹配相对lib目录的src来源
src: '**/*.js', //实际的匹配模式
dest: 'build/', //目标路径前缀
ext: '.min.js' //目标文件路径中文件的扩展名.
}
]
}
}
});

模版

 


 

使用<% %>分隔符指定的模会在任务从它们的配置中读取相应的数据时将自动扩展扫描。模板会被递归的展开,直到配置中不再存在遗留的模板相关的信息(与模板匹配的)。

 

整个配置对象决定了属性上下文(模板中的属性)。此外,在模板中使用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'
}
},
//用于任务配置模板的任意属性
foo: 'c',
bar: 'b<%= foo %>d', //'bcd'
baz: 'a<%= bar %>e', //'abcde'
qux: ['foo/*.js', 'bar/*.js']
});

导入外部数据


在下面的Gruntfile中,项目的元数据是从package.json文件中导入到Grunt配置中的,并且grunt-contrib-uglify插件的uglify任务被配置用于压缩一个源文件以及使用该元数据动态的生成一个banner注释。

Grunt有grunt.file.readJSONgrunt.file.readYAML两个方法分别用于引入JSON和YAML数据。

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/* <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
dist: {
src: 'src/<%= pkg.name %>.js',
dest: 'dist/<%= pkg.name %>.min.js'
}
}
});

到此,Grunt的任务配置相关的知识都已涉及到了,但不同的Grunt的任务其配置都略有不同,不过针对不同的插件任务配置,可以查阅每个Grunt插件的说明文档。

对于初学者来说,或许上面的内容有很多都理解不过来,其实我也一样,不过不用担心,当你看得多,做得多,到一定的时候自然就整得明白是怎么一回事。现在我们返回到示例中。根据上面的内容,给"my-grunt"项目创建了grunt-contrib-uglifygrunt-contrib-sass两个任务,并且完成了一些简单的配置:

//Wrapper函数
module.exports = function(grunt) {

// 配置项目
grunt.initConfig({
// 配置任务
pkg:grunt.file.readJSON('package.json'),

uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/js/<%= pkg.name %>.js',
dest: 'build/js/<%= pkg.name %>.min.js'
}
},
sass: {
dist: {
options: {
style: 'compressed'
},
expand: true,
cwd: 'src/styles/sass/',
src: ['*.scss'],
dest: 'build/styles/css',
ext: '.css'
},
dev: {
options: {
style: 'expanded',
debugInfo: true,
lineNumbers: true
},
expand: true,
cwd: 'src/styles/sass/',
src: ['*.scss'],
dest: 'build/styles/css',
ext: '.css'
}
}
});

// 加载任务
//加载提供“uglify”任务的插件
grunt.loadNpmTasks('grunt-contrib-uglify');
//加载提供"sass"任务插件
grunt.loadNpmTasks('grunt-contrib-sass');

// 默认任务.
grunt.registerTask('default', ['uglify','sass']);

};

仅这样Grunt不会自动执行任何任务,因为我们只完成了配置,但没有安装Grunt里面的任务。如果你需要安装,只需要在命令终端执行:

$ npm install

在终端中,看到下面这样的结束信息,表示安装成功:

...
grunt-contrib-uglify@0.2.5 node_modules/grunt-contrib-uglify
├── grunt-lib-contrib@0.6.1 (zlib-browserify@0.0.1)
└── uglify-js@2.4.3 (uglify-to-browserify@1.0.1, async@0.2.9, optimist@0.3.7, source-map@0.1.31)

同时在项目的根目录下会自生成一个node_modules目录,而且在Grunt中配置的相关的任务都放在这个目录中。

+my-grunt
|----Gruntfile.js
+----node_modules
|----+----grunt
|----+----grunt-contrib-sass
|----+----grunt-contrib-uglify
|----package.json

在整个项目中,uglify任务是用来压缩项目中.js文件,sass任务是用来编译项目中的.scss文件。为了证明我们创建的任务是正确的,我们在项目中创建一个js文件和.scss文件来检验是否成功。并且将.js文件放置在src/js目录下,.scss文件放置在src/styles/sass目录下。同时我们创建两测试文件:my-grunt.jsmain.scss

/my-grunt.js

(function($){
var str = "<p>hello grunt!</p>";
$("body").append(str);
})(jQuery)

//main.scss
$color: #f36;
$bgColor: #fff;
$unit: 30px;

body {
background-color: $bgColor;
}
.container {
margin: ($unit / 3) auto;
width: $unit * 3;
}

 

注:因为我们uglify任务中使用是<% pk.name %>模板,而这里面的name对应的就是package.json中的name参数。因此需要创建一个my-grunt.js

 

你要执行Grunt任务的时候,只需要在命令行中执行:

$ grunt

这个时候,你在终端可以看到对应的信息:

Running "uglify:build" (uglify) task
File "build/js/my-grunt.min.js" created.

Running "sass:dist" (sass) task

Running "sass:dev" (sass) task

Done, without errors.

同时项目自动会生成一个名叫build的目录,而压缩的.min.js文件对应放置在build/js目录下;编译出来的.css放置在build/style/css下:

+my-grunt
+----build
+----+----js
+----+----|----my-grunt.min.js
+----+----styles
+----+----+----css
+----+----+----|----main.css
|----Gruntfile.js
+----node_modules
|----package.json
+----src
+----+----js
+----+----|----my-grunt.js
+----+----styles
+----+----+----sass
+----+----+----|----main.scss

因为我们在Grunt中配置了默认任务,如果没有设置默认任务,必须需要单独执行:

$ grunt uglify
$ grunt sass

结论:

 


本文主要向大家介绍了Grunt项目中Gruntfile.js文件的配置以及如何在这个文件中配置对应的任务。并且详细介绍了配置任务中的一些参数与细节的使用。最后通过一个简单示例,向大家介绍了如何使用Grunt配置任务。虽然文章中有很多部分对于初学者来说理解有一定难度,但我相信,随着时间的推移,会慢慢搞懂文章中介绍的所有知识。最后希望本文对初学者有所帮助。

 

 

文中有很多部分摘自ToobugBasecss翻译的Grunt中文文档。在此特别感谢他们为我们提供中文版本的API。

 

打完收工。。。。。