Gruntfile 实例

Gruntfile 实例

下面就针对一个 Gruntfile 案例做简单分析,也可以作为一个实例使用:

module.exports = function(grunt) {

  grunt.initConfig({
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
      options: {
        globals: {
          jQuery: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files %>'],
      tasks: ['jshint']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['jshint']);

};

在页面底部是这个 Gruntfile 实例的完整内容,如果你按顺序阅读本文的话,可以跟随我们一步步分析这个文件中的每一部分。我们会用到以下5个 Grunt 插件:

第一部分是"wrapper" 函数,它包含了整个Grunt配置信息。

module.exports = function(grunt) {
}

在这个函数中,我们可以初始化 configuration 对象:

grunt.initConfig({
});

接下来可以从package.json 文件读入项目配置信息,并存入pkg 属性内。这样就可以让我们访问到package.json文件中列出的属性了,如下:

pkg: grunt.file.readJSON('package.json')

到目前为止我们就可以看到如下配置:

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json')
  });
};

现在我们就可以为我们的每个任务来定义相应的配置(逐个定义我们为项目定义的任务配置),然后每个任务的配置对象作为Grunt配置对象(即grunt.initConfig({})所接受的配置对象)的属性,并且这个属性名称与任务名相同。因此"concat"任务就是我们的配置对象中的"concat"键(属性)。下面便是我的"concat"任务的配置对象。

concat: {
  options: {
    // 定义一个用于插入合并输出文件之间的字符
    separator: ';'
  },
  dist: {
    // 将要被合并的文件
    src: ['src/**/*.js'],
    // 合并后的JS文件的存放位置
    dest: 'dist/<%= pkg.name %>.js'
  }
}

注意我是如何引用JSON文件(也就是我们在配置对象顶部引入的配置文件)中的name属性的。这里我使用pkg.name来访问我们刚才引入并存储在pkg属性中的package.json文件信息,它会被解析为一个JavaScript对象。

Grunt自带的有一个简单的模板引擎用于输出配置对象(这里是指package.json中的配置对象)属性值,这里我让concat任务将所有存在于src/目录下以.js结尾的文件合并起来,然后存储在dist目录中,并以项目名来命名。

现在我们来配置uglify插件,它的作用是压缩(minify)JavaScript文件:

uglify: {
  options: {
    // 此处定义的banner注释将插入到输出文件的顶部
    banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
  },
  dist: {
    files: {
      'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
    }
  }
}

这里我们让uglify在dist/目录中创建了一个包含压缩结果的JavaScript文件。注意这里我使用了<%= concat.dist.dest>,因此uglify会自动压缩concat任务中生成的文件。

QUnit插件的设置非常简单。 你只需要给它提供用于测试运行的文件的位置,注意这里的QUnit是运行在HTML文件上的。

qunit: {
  files: ['test/**/*.html']
},

JSHint插件的配置也很简单:

jshint: {
  // define the files to lint
  files: ['gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
  // configure JSHint (documented at http://www.jshint.com/docs/)
  options: {
      // more options here if you want to override JSHint defaults
    globals: {
      jQuery: true,
      console: true,
      module: true
    }
  }
}

JSHint只需要一个文件数组(也就是你需要检测的文件数组), 然后是一个options对象(这个对象用于重写JSHint提供的默认检测规则)。

你可以到JSHint官方文档站点中查看完整的文档。如果你乐于使用JSHint提供的默认配置,那么在Gruntfile中就不需要重新定义它们了。

最后,我们来看看watch插件:

watch: {
  files: ['<%= jshint.files %>'],
  tasks: ['jshint', 'qunit']
}

你可以在命令行使用grunt watch来运行这个任务。当它检测到任何你所指定的文件(在这里我使用了JSHint任务中需要检测的相同的文件)发生变化时,它就会按照你所指定的顺序执行指定的任务(在这里我指定了jshint和qunit任务)。

最后, 我们还要加载所需要的Grunt插件。 它们应该已经全部通过npm安装好了。

grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');

最后设置了一些task。最重要的是default任务:

// 在命令行上输入"grunt test",test task就会被执行。
grunt.registerTask('test', ['jshint', 'qunit']);

// 只需在命令行上输入"grunt",就会执行default task
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);

下面便是最终完成的 Gruntfile 文件:

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options: {
        separator: ';'
      },
      dist: {
        src: ['src/**/*.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
      },
      dist: {
        files: {
          'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
        }
      }
    },
    qunit: {
      files: ['test/**/*.html']
    },
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
      options: {
        //这里是覆盖JSHint默认配置的选项
        globals: {
          jQuery: true,
          console: true,
          module: true,
          document: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files %>'],
      tasks: ['jshint', 'qunit']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');

  grunt.registerTask('test', ['jshint', 'qunit']);

  grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);

};

Gruntfile.js实例:grunt-contrib-cssmin模块

现在通过cssmin模块,演示如何编写Gruntfile.js文件。cssmin模块的作用是最小化CSS文件。

首先,在项目的根目录下安装该模块。

npm install grunt-contrib-cssmin --save-dev

然后,新建文件Gruntfile.js。

module.exports = function(grunt) {

  grunt.initConfig({
    cssmin: {
      minify: {
        expand: true,
		cwd: 'css/',
		src: ['*.css', '!*.min.css'],
		dest: 'css/',
		ext: '.min.css'
	  },
      combine: {
	    files: {
		  'css/out.min.css': ['css/part1.min.css', 'css/part2.min.css']
		}
	  }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-cssmin');

  grunt.registerTask('default', ['cssmin:minify','cssmin:combine']);

};

下面详细解释上面代码中的三个方法,下面一个个来看。

1、grunt.loadNpmTasks

grunt.loadNpmTasks方法载入模块文件。

  grunt.loadNpmTasks('grunt-contrib-cssmin');

你需要使用几个模块,这里就要写几条grunt.loadNpmTasks语句,将各个模块一一加载。

如果加载模块很多,这部分会非常冗长。而且,还存在一个问题,就是凡是在这里加载的模块,也同时出现在package.json文件中。如果使用npm命令卸载模块以后,模块会自动从package.json文件中消失,但是必须手动从Gruntfile.js文件中清除,这样很不方便,一旦忘记,还会出现运行错误。这里有一个解决办法,就是安装load-grunt-tasks模块,然后在Gruntfile.js文件中,用下面的语句替代所有的grunt.loadNpmTasks语句。

require('load-grunt-tasks')(grunt);

这条语句的作用是自动分析package.json文件,自动加载所找到的grunt模块。

2、grunt.initConfig

grunt.initConfig方法用于模块配置,它接受一个对象作为参数。该对象的成员与使用的同名模块一一对应。由于我们要配置的是cssmin模块,所以里面有一个cssmin成员(属性)。

cssmin(属性)指向一个对象,该对象又包含多个成员。除了一些系统设定的成员(比如options),其他自定义的成员称为目标(target)。一个模块可以有多个目标(target),上面代码里面,cssmin模块共有两个目标,一个是“minify”,用于压缩css文件;另一个是“combine”,用于将多个css文件合并一个文件。

每个目标的具体设置,需要参考该模板的文档。就cssmin来讲,minify目标的参数具体含义如下:

  • expand:如果设为true,就表示下面文件名的占位符(即*号)都要扩展成具体的文件名。

  • cwd:需要处理的文件(input)所在的目录。

  • src:表示需要处理的文件。如果采用数组形式,数组的每一项就是一个文件名,可以使用通配符。

  • dest:表示处理后的文件名或所在目录。

  • ext:表示处理后的文件后缀名。

除了上面这些参数,还有一些参数也是grunt所有模块通用的。

  • filter:一个返回布尔值的函数,用于过滤文件名。只有返回值为true的文件,才会被grunt处理。

  • dot:是否匹配以点号(.)开头的系统文件。

  • makeBase:如果设置为true,就只匹配文件路径的最后一部分。比如,a?b可以匹配/xyz/123/acb,而不匹配/xyz/acb/123。

关于通配符,含义如下:

  • *:匹配任意数量的字符,不包括/。

  • ?:匹配单个字符,不包括/。

  • **:匹配任意数量的字符,包括/。

  • {}:允许使用逗号分隔的列表,表示“or”(或)关系。

  • !:用于模式的开头,表示只返回不匹配的情况。

比如,foo/*.js匹配foo目录下面的文件名以.js结尾的文件,foo/**/*.js匹配foo目录和它的所有子目录下面的文件名以.js结尾的文件,!*.css表示匹配所有后缀名不为“.css”的文件。

使用通配符设置src属性的更多例子:

{src: 'foo/th*.js'}grunt-contrib-uglify
{src: 'foo/{a,b}*.js'}
{src: ['foo/a*.js', 'foo/b*.js']}

至于combine目标,就只有一个files参数,表示输出文件是css子目录下的out.min.css,输入文件则是css子目录下的part1.min.css和part2.min.css。

files参数的格式可以是一个对象,也可以是一个数组。

files: {
        'dest/b.js': ['src/bb.js', 'src/bbb.js'],
        'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'],
},
// or
files: [
        {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
        {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'},
],

如果minify目标和combine目标的属性设置有重合的部分,可以另行定义一个与minify和combine平行的options属性。

grunt.initConfig({
    cssmin: {
	  options: { /* ... */ },
      minify: { /* ... */ },
      combine: { /* ... */ }
    }
  });

3、grunt.registerTask

grunt.registerTask方法定义如何调用具体的任务。“default”任务表示如果不提供参数,直接输入grunt命令,则先运行“cssmin:minify”,后运行“cssmin:combine”,即先压缩再合并。如果只执行压缩,或者只执行合并,则需要在grunt命令后面指明“模块名:目标名”。

grunt # 默认情况下,先压缩后合并 
grunt cssmin:minify # 只压缩不合并 
grunt css:combine # 只合并不压缩

如果不指明目标,只是指明模块,就表示将所有目标依次运行一遍。

grunt cssmin