配置任务
本指南解释了如何使用 Gruntfile
为您的项目配置任务。如果您不知道 Gruntfile
是什么,请阅读入门指南并查看Gruntfile 示例。
Grunt 配置
任务配置在您的 Gruntfile
中通过 grunt.initConfig
方法指定。此配置主要位于以任务命名的属性下,但也可能包含任何任意数据。只要属性不与您的任务所需的属性冲突,它们将被忽略。
此外,因为这是 JavaScript,所以您不限于 JSON;您可以在此处使用任何有效的 JavaScript。如果需要,您甚至可以以编程方式生成配置。
grunt.initConfig({
concat: {
// concat task configuration goes here.
},
uglify: {
// uglify task configuration goes here.
},
// Arbitrary non-task-specific properties.
my_property: 'whatever',
my_src_files: ['foo/*.js', 'bar/*.js'],
});
任务配置和目标
运行任务时,Grunt 会在同名属性下查找其配置。多任务可以有多个配置,使用任意命名的“目标”定义。在下面的示例中,concat
任务具有 foo
和 bar
目标,而 uglify
任务只有一个 bar
目标。
grunt.initConfig({
concat: {
foo: {
// concat task "foo" target options and files go here.
},
bar: {
// concat task "bar" target options and files go here.
},
},
uglify: {
bar: {
// uglify task "bar" target options and files go here.
},
},
});
像 grunt concat:foo
或 grunt concat:bar
这样同时指定任务和目标将只处理指定目标的配置,而运行 grunt concat
将迭代所有目标,依次处理每个目标。请注意,如果任务已使用 grunt.task.renameTask 重命名,Grunt 将在配置对象中查找具有新任务名称的属性。
选项
在任务配置中,可以指定 options
属性来覆盖内置默认值。此外,每个目标都可以有一个特定于该目标的 options
属性。目标级选项将覆盖任务级选项。
options
对象是可选的,如果不需要,可以省略。
grunt.initConfig({
concat: {
options: {
// Task-level options may go here, overriding task defaults.
},
foo: {
options: {
// "foo" target options may go here, overriding task-level options.
},
},
bar: {
// No options specified; this target will use task-level options.
},
},
});
文件
因为大多数任务都执行文件操作,所以 Grunt 具有强大的抽象来声明任务应该操作哪些文件。有几种方法可以定义src-dest(源-目标)文件映射,提供不同程度的冗长度和控制。任何多任务都将理解以下所有格式,因此请选择最能满足您需求的格式。
所有文件格式都支持 src
和 dest
,但紧凑和文件数组格式支持一些额外的属性
filter
有效的 fs.Stats 方法名称 或传递匹配的src
文件路径并返回true
或false
的函数。查看示例nonull
如果设置为true
,则操作将包括不匹配的模式。结合 grunt 的--verbose
标志,此选项可以帮助调试文件路径问题。dot
允许模式匹配以句点开头的文件名,即使该模式在该位置没有明确的句点。matchBase
如果设置,则不带斜杠的模式将与路径的基本名称匹配,如果它包含斜杠。例如,a?b
将匹配路径/xyz/123/acb
,但不匹配/xyz/acb/123
。expand
处理动态 src-dest 文件映射,有关更多信息,请参阅“动态构建文件对象”。- 其他属性将作为匹配选项传递到底层库。有关更多选项,请参阅 node-glob 和 minimatch 文档。
Grunt 和任务选项之间的区别
大多数任务都执行文件操作,因此 Grunt 提供了一个内置的基础结构来检索任务应该处理的文件。这样做的好处是任务作者不必再次实现此逻辑。为了允许用户指定这些文件,Grunt 提供了 nonull
和 filter
等选项。
除了要处理的文件之外,每个任务都有其自身的特定需求。任务作者可能希望允许其用户配置一些选项来覆盖默认行为。这些特定于任务的选项不应与前面描述的 Grunt 选项混淆。
为了进一步说明这种差异,让我们看一个使用 grunt-contrib-jshint 的示例
grunt.initConfig({
jshint: {
ignore_warning: {
options: {
'-W015': true,
},
src: 'js/**',
filter: 'isFile'
}
}
});
此配置使用 Grunt 选项 src
和 filter
来指定要处理的文件。它还使用 grunt-contrib-jshint 特定于任务的选项 -W015
来忽略特定警告(代码为 W015
的警告)。
紧凑格式
此格式允许每个目标有一个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'},
],
},
},
});
旧格式
dest-as-target 文件格式是多任务和目标存在之前的遗留物,其中目标文件路径实际上是目标名称。不幸的是,因为目标名称是文件路径,所以运行 grunt task:target
可能很麻烦。此外,您不能为每个 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: ['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);
},
},
},
});
另一个示例——利用了通配符和expand: true 功能——允许您避免覆盖目标中已存在的文件
grunt.initConfig({
copy: {
templates: {
files: [{
expand: true,
cwd: ['templates/css/'], // Parent folder of original CSS templates
src: '**/*.css', // Collects all `*.css` files within the parent folder (and its subfolders)
dest: 'src/css/', // Stores the collected `*.css` files in your `src/css/` folder
filter: function (dest) { // `dest`, in this instance, is the filepath of each matched `src`
var cwd = this.cwd, // Configures variables (these are documented for your convenience only)
src = dest.replace(new RegExp('^' + cwd), '');
dest = grunt.task.current.data.files[0].dest;
return (!grunt.file.exists(dest + src)); // Copies `src` files ONLY if their destinations are unoccupied
}
}]
}
}
});
请记住,上述技术在检查目标是否存在时不会考虑rename 属性。
通配符模式
单独指定所有源文件路径通常是不切实际的,因此 Grunt 通过内置的 node-glob 和 minimatch 库支持文件名扩展(也称为通配)。
虽然这不是关于通配符模式的全面教程,但请知道在文件路径中
*
匹配任意数量的字符,但不匹配/
?
匹配单个字符,但不匹配/
**
匹配任意数量的字符,包括/
,只要它是路径部分中唯一的内容{}
允许使用逗号分隔的“或”表达式列表- 模式开头的
!
将否定匹配
大多数人需要知道的是,foo/*.js
将匹配 foo/
子目录中所有以 .js
结尾的文件,但 foo/**/*.js
将匹配 foo/
子目录及其所有子目录中所有以 .js
结尾的文件。
此外,为了简化原本复杂的通配符模式,Grunt 允许指定文件路径或通配符模式的数组。模式按顺序处理,以 !
为前缀的匹配项从结果集中排除匹配的文件。结果集是唯一的。
例如
// You can specify single files:
{src: 'foo/this.js', dest: ...}
// Or arrays of files:
{src: ['foo/this.js', 'foo/that.js', 'foo/the-other.js'], dest: ...}
// Or you can generalize with a glob pattern:
{src: 'foo/th*.js', dest: ...}
// This single node-glob pattern:
{src: 'foo/{a,b}*.js', dest: ...}
// Could also be written like this:
{src: ['foo/a*.js', 'foo/b*.js'], dest: ...}
// All .js files, in foo/, in alpha order:
{src: ['foo/*.js'], dest: ...}
// Here, bar.js is first, followed by the remaining files, in alpha order:
{src: ['foo/bar.js', 'foo/*.js'], dest: ...}
// All files except for bar.js, in alpha order:
{src: ['foo/*.js', '!foo/bar.js'], dest: ...}
// All files in alpha order, but with bar.js at the end.
{src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...}
// Templates may be used in filepaths or glob patterns:
{src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}
// But they may also reference file lists defined elsewhere in the config:
{src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}
有关 glob 模式语法的更多信息,请参阅 node-glob 和 minimatch 文档。
动态构建文件对象
当您要处理许多单独的文件时,可以使用一些附加属性来动态构建文件列表。可以在紧凑和文件数组映射格式中指定这些属性。
expand
设置为 true
将启用以下属性
cwd
所有src
匹配都相对于(但不包括)此路径。src
要匹配的模式,相对于cwd
。dest
目标路径前缀。ext
在生成的dest
路径中用此值替换任何现有扩展名。extDot
用于指示指示扩展名的句点所在的位置。可以取'first'
(扩展名从文件名的第一个句点开始)或'last'
(扩展名从最后一个句点开始),默认设置为'first'
[在 0.4.3 中添加]flatten
从生成的dest
路径中删除所有路径部分。rename
嵌入一个自定义函数,该函数返回一个包含新目标和文件名的字符串。为每个匹配的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
},
],
},
},
});
rename 属性
rename
属性是唯一的,因为它唯一有效的值是一个 JavaScript 函数。尽管该函数返回一个字符串,但您不能简单地使用字符串作为 rename
的值(这样做会导致错误:对象 # 的属性 'rename' 不是函数
)。在以下示例中,copy
任务将创建 README.md 的备份。
grunt.initConfig({
copy: {
backup: {
files: [{
expand: true,
src: ['docs/README.md'], // The README.md file has been specified for backup
rename: function () { // The value for rename must be a function
return 'docs/BACKUP.txt'; // The function must return a string with the complete destination
}
}]
}
}
});
调用该函数时,会传入 dest
和匹配的 src
路径,并可用于返回输出字符串。在以下示例中,文件从 dev
文件夹复制到 dist
文件夹,并重命名为删除单词“beta”。
grunt.initConfig({
copy: {
production: {
files: [{
expand: true,
cwd: 'dev/',
src: ['*'],
dest: 'dist/',
rename: function (dest, src) { // The `dest` and `src` values can be passed into the function
return dest + src.replace('beta',''); // The `src` is being renamed; the `dest` remains the same
}
}]
}
}
});
如果多个匹配的 src
路径被重命名为相同的目标(即,如果两个不同的文件被重命名为同一个文件),则每个输出都将被添加到其源数组中。
模板
使用 <% %>
分隔符指定的模板将在任务从配置中读取它们时自动扩展。模板会递归扩展,直到不再存在。
整个配置对象是解析属性的上下文。此外,grunt
及其方法在模板内部可用,例如 <%= grunt.template.today('yyyy-mm-dd') %>
。
<%= prop.subprop %>
扩展为配置中prop.subprop
的值,无论类型如何。像这样的模板不仅可以用来引用字符串值,还可以用来引用数组或其他对象。<% %>
执行任意内联 JavaScript 代码。这在控制流或循环时很有用。
鉴于下面示例 concat
任务配置,运行 grunt concat:sample
将通过将横幅 /* abcde */
与所有匹配 foo/*.js
+ bar/*.js
+ baz/*.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'
},
},
// Arbitrary properties used in task configuration templates.
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
任务被配置为压缩源文件并使用该元数据动态生成注释横幅。
Grunt 具有 grunt.file.readJSON
和 grunt.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'
}
}
});