Sample 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']);
};
需求
每个项目都有其特定需求,但大多数项目有一些共同点。在本指南中,我们将介绍几个 Grunt 插件来自动化基本需求。最终目标是教你如何配置这些 Grunt 插件,以便在你的项目中使用它们。
为了举例说明,假设你正在创建一个 JavaScript 库。典型的文件夹结构包括以下文件夹:src
、dist
和 test
。src
文件夹(有时称为 app
)包含你编写的库的源代码。dist
文件夹(有时称为 build
)包含发行版,即源代码的压缩版本。压缩文件是指删除了所有不必要的字符,如空格、换行、注释,同时不影响源代码的功能。压缩的源代码对项目用户特别有用,因为它减少了需要传输的数据量。最后,test
文件夹包含用于测试项目的代码。在接下来的章节中创建 Gruntfile
配置时,将使用这种设置。
在开发库和发布新版本的过程中,有一些需要定期执行的任务。例如,你可能想确保你编写的代码遵循最佳实践,或者确保你编写的代码不会导致意外行为。为此,你可以使用一个名为 JSHint 的工具。Grunt 有一个官方插件叫 grunt-contrib-jshint,我们将在这个示例中采用。特别是,你可能想确保在修改代码时不会破坏任何规则或最佳实践。因此,一个好的策略是在每次更改时检查代码。为此,我们将介绍一个名为 grunt-contrib-watch 的 Grunt 插件。后者在添加、更改或删除文件时运行预定义的任务,如 grunt-contrib-jshint
。
仅仅检查源代码是否遵循最佳实践是不够的,无法保证其稳定性和没有 bug。要创建一个健壮的项目,你需要对其进行测试。有几个可以采用的库,如 QUnit 或 Jasmine。在本指南中,我们描述如何配置 QUnit,特别是 grunt-contrib-qunit,来测试你的代码。
在分发你的作品时,你希望提供一个尽可能小的版本。要创建压缩版本,你需要像 grunt-contrib-uglify 这样的 Grunt 插件。此外,除非你开发的项目非常小,否则你可能已经将代码拆分为多个文件。虽然这对开发者来说是一个好的做法,但用户希望只包含一个文件。因此,在压缩代码之前,你应该将源文件连接起来创建一个单一文件。为实现这一目标,你需要像 grunt-contrib-concat 这样的 Grunt 插件。
总结一下,在本指南中,我们将使用以下五个 Grunt 插件:
- grunt-contrib-uglify
- grunt-contrib-qunit
- grunt-contrib-concat
- grunt-contrib-jshint
- grunt-contrib-watch
如果你对最终结果感到好奇,完整的 Gruntfile
可以在本页底部找到。
设置 Gruntfile
第一部分是"包装器"函数,它封装了你的 Grunt 配置。
module.exports = function(grunt) {
};
在该函数中,我们可以初始化配置对象:
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-contrib-concat
的配置位于配置对象的 concat
键下,如下所示:
concat: {
options: {
// 定义要放在连接输出的每个文件之间的字符串
separator: ';'
},
dist: {
// 要连接的文件
src: ['src/**/*.js'],
// 生成的 JS 文件的位置
dest: 'dist/<%= pkg.name %>.js'
}
}
注意在上面的代码片段中,我们引用了 JSON 文件中的 name
属性。我们通过使用 pkg.name
来访问它,因为之前我们定义了 pkg
属性为加载 package.json
文件的结果,然后解析为 JavaScript 对象。Grunt 有一个简单的模板引擎来输出配置对象中属性的值。在这里,我们告诉 concat
任务连接所有存在于 src/
中并以 .js
结尾的文件。
现在让我们配置 grunt-contrib-uglify
插件,它可以压缩 JavaScript 代码:
uglify: {
options: {
// 横幅插入在输出的顶部
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
files: {
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
}
}
这段代码告诉 grunt-contrib-uglify
在 dist/
中创建一个文件,该文件包含压缩 JavaScript 文件的结果。在这里,我们使用 <%= concat.dist.dest %>
以便 uglify 压缩 concat 任务生成的文件。
到目前为止,我们已经配置了插件来创建库的发行版本。现在是时候使用 grunt-contrib-qunit
来自动化代码测试了。为此,我们需要指定测试运行器文件的位置,这些是 QUnit 运行的 HTML 文件。结果代码如下:
qunit: {
files: ['test/**/*.html']
},
完成后,是时候设置配置以确保项目的代码遵循最佳实践了。JSHint 是一个可以检测问题或潜在问题的工具,如高圈复杂度、使用相等运算符而不是严格相等运算符,以及未使用的变量和函数的定义。
我们建议使用 grunt-contrib-jshint
分析项目的所有 JavaScript 文件,包括 Gruntfile
和测试文件。grunt-contrib-jshint
的配置示例如下:
jshint: {
// 定义要检查的文件
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
// 配置 JSHint(在 http://www.jshint.com/docs/ 有文档)
options: {
// 如果要覆盖 JSHint 默认值,可以在这里添加更多选项
globals: {
jQuery: true,
console: true,
module: true
}
}
}
这个插件接受一个文件数组和一个选项对象。这些都在 JSHint 网站上有文档。如果你对插件默认值感到满意,则无需在 Gruntfile 中重新定义它们。
最后要配置的插件是 grunt-contrib-watch
。我们将使用它在添加、删除或修改 JavaScript 文件时立即运行 jshint
和 qunit
任务。当它检测到指定的文件发生变化(这里,我们使用与 JSHint 相同的文件)时,它将按顺序运行你指定的任务。这可以在命令行中使用 grunt watch
运行。
将前面的描述转换为 grunt-contrib-watch
的配置会得到以下代码片段:
watch: {
files: ['<%= jshint.files %>'],
tasks: ['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');
最后,设置一些任务。这些任务中最重要的是默认任务:
// 这将通过在命令行上键入 "grunt test" 来运行
grunt.registerTask('test', ['jshint', 'qunit']);
// 默认任务可以通过在命令行上键入 "grunt" 来运行
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
默认任务是在调用 Grunt
且不指定要执行的任务时执行的任务(grunt
)。
最终的 Gruntfile
如果你正确地遵循了本指南,你应该有以下 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']);
};