Grunt Configuration Tasks

前言

在翻译官文的前提下,增加自己的理解。http://gruntjs.com/configuring-tasks

Grunt Configuration

1
2
3
4
5
6
7
8
9
10
11
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'],
});
  1. Task configuration is specified in your Gruntfile via the grunt.initConfig method
  2. because this is JavaScript, you’re not limited to JSON; you may use any valid JavaScript here.

Task Configuration: Tasks and Targets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.
},
},
});

the concat task has foo and bar targets, while the uglify task only has a bar target。

这里可以看出TaskTarget之间的定义,一个 Configuration 中可以有多个Task,而每个Task可以拥有多个Target

grunt concat:foo或者grunt concat:bar命令可以只执行该指定的 Target;
grunt concat命令将会同时执行foobar两个 Target。

Options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.
},
},
});

Inside a task configuration, an options property may be specified to override built-in defaults. In addition, each target may have an options property which is specific to that target. Target-level options will override task-level options.

options 属性可以覆盖内置的默认选项,每一个 target 都可以定义 options,不过 Target-level options 将会覆盖 task-level options。

Files

这里指源地址src和目的地址desc中的Files

Difference Between Grunt and Task Options

In addition to the files to work on, each task has its own specific needs. A task author may want to allow its user to configure some options to override the default behavior. These task-specific options shall not be confused with the Grunt options described before.
task 的作者希望使用他的人,可以通过options去重载它的默认行为。

1
2
3
4
5
6
7
8
9
10
11
grunt.initConfig({
jshint: {
ignore_warning: {
options: {
'-W015': true,
},
src: 'js/**',
filter: 'isFile'
}
}
});

This configuration employs the Grunt options src and filter to specify the files to process. It also uses the grunt-contrib-jshint task-specific option -W015 to ignore a specific warning (the one having code W015).

补充:总觉得官方这里的标题有问题,这里不就是再进一步描述 options 的作用吗?怎么和 Difference 扯上关系了?

Compact Format

This form allows a single src-dest (source-destination) file mapping per-target

1
2
3
4
5
6
7
8
9
10
11
12
13
grunt.initConfig({
jshint: {
foo: {
src: ['src/aa.js', 'src/aaa.js']
},
},
concat: {
bar: {
src: ['src/bb.js', 'src/bbb.js'],
dest: 'dest/b.js',
},
},
});

  1. It is most commonly used for read-only tasks, like grunt-contrib-jshint, where a single src property is needed, and no dest key is relevant。
    可以之定义 src,表示只读,jshint只需要源文件输入,检查代码的语法。
  2. 也可以是src-destconcat合并源文件到输出目录,所以需要srcdest

File Object Format

This form supports multiple src-dest mappings per-target, where the property name is the destination file, and its value is the source file(s).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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'],
},
},
},
});

属性名称是目的文件'dest/a.js',属性值是源文件'src/aa.js', 'src/aaa.js',也可以通过File Array Format或者Older Format进行配置

File Array Format

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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'},
],
},
},
});

Older Format

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

Custom Filter Function

The filter property can help you target files with a greater level of detail.

  1. Simply use a valid fs.Stats method name

    1
    2
    3
    4
    5
    6
    7
    8
    grunt.initConfig({
    clean: {
    foo: {
    src: ['tmp/**/*'],
    filter: 'isFile',
    },
    },
    });
  2. create your own filter function and return true or false whether the file should be matched
    For example the following will only clean folders that are empty:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    grunt.initConfig({
    clean: {
    foo: {
    src: ['tmp/**/*'],
    filter: function(filepath) {
    return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0);
    },
    },
    },
    });
  3. utilizes the globbing and expand: true features—allows you to avoid overwriting files which already exist in the destination

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    }
    }]
    }
    }
    });

Globbing patterns

  • * matches any number of characters, but not /
  • ? matches a single character, but not /
  • ** matches any number of characters, including /, as long as it’s the only thing in a path part
  • {} allows for a comma-separated list of “or” expressions
  • ! at the beginning of a pattern will negate the match

foo/*.js will match all files ending with .js in the foo/ subdirectory
foo/**/*.js will match all files ending with .js in the foo/ subdirectory and all of its subdirectories.

more examples,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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: ...}

For more on glob pattern syntax, see the node-glob and minimatch documentation.

Building the files object dynamically

When you want to process many individual files, a few additional properties may be used to build a files list dynamically;These properties may be specified in both Compact and Files Array mapping formats.。当你需要处理大量的文件的时候,一些额外的属性可以帮助你动态的进行处理;这些属性可以再CompactFile Array里面指定。
expand Set to true will enable the following properties:

  • cwd All src matches are relative to (but don’t include) this path.
    意思就是说src的寻址路径的根路径由cwd指定
  • src Pattern(s) to match, relative to the cwd.
  • dest Destination path prefix.
  • ext Replace any existing extension with this value in generated dest paths.
    ext属性指定的扩展名将替换当前文件的扩展名,并用此来生成dest文件路径。
  • extDot Used to indicate where the period indicating the extension is located. Can take either ‘first’ (extension begins after the first period in the file name) or ‘last’ (extension begins after the last period), and is set by default to ‘first’ [Added in 0.4.3]
    很简单,就是从后缀名中的第几个.开始替换,比如,我们有文件myname.hello.world.js,如果是 extDot 的值是first,那么从.hello.world.js开始替换,如果是last,那么从.js开始替换。
  • flatten Remove all path parts from generated dest paths.
    从 dest paths 中删除所有的path parts。(TODO: path parts 指的是什么?)
  • rename Embeds a customized function, which returns a string containing the new destination and filename. This function is called for each matched src file (after extension renaming and flattening). More information
    rename嵌入一个自定义的 function,返回一个包含新的 destination 和 文件名的 string,每一个匹配的 src 文件都会被执行,rename 是在 extension 重命名和 flattering 之后执行。

Any combination of static src-dest and dynamic src-dest file mappings may be specified.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
},
],
},
},
});

该用例同时包含了静态和动态文件处理的例子,静态处理流程必须手动指定 src 和 dest 的文件和路径;而动态处理可以动态的检测 src 中需要处理的文件,并且按照约定输出,上面的例子中就是动态的去获取lib/目录中的源文件,并且从后缀名的第一个.将其重命名为后缀名为.min.js,并输出到build/目录中。

The rename property

  1. Sample 1: the copy task will create a backup of README.md
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    }
    }]
    }
    }
    });

When the function is called, the dest and matched src path are passed in and can be used for returning the output string.
当该方法被调用后,dest 和 匹配的 src path 将会作为入参( 补充,rename: function(dest, src),但是上述用例并没有将他们作为入参来调用),并用来生成返回的 string;( 注意,这里返回的 string 就是最终要输出的 destination 目的地址。);
进一步解读,这个用例到底是什么意思?

1
src: ['docs/README.md'],    // The README.md file has been specified for backup

首先指定了需要被copy的 src 文件,这里只指定了一个文件docs/README.md

1
2
3
rename: function () {       // The value for rename must be a function
return 'docs/BACKUP.txt'; // The function must return a string with the complete destination
}

什么意思?其实就是将README.md拷贝并重命名为BACKUP.txt并输出到docs/目的目录中。这个例子比较特殊,只是重命名了一个文件并作为输出。要注意的是,rename这个方法比较特殊,不仅仅是重命名,而且需要输出 destination 的相关的信息。

  1. Sample 2: files are copied from the dev folder to the dist folder, and renamed to have the word “beta” removed
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
    }
    }]
    }
    }
    });

什么意思,起到了什么样的作用?将dev/中的源文件去掉’beta’字样以后,拷贝到dest目录中;所以,这里的样例很可能是这样的

1
2
3
├── dev
│   ├── hellobeta.js
│   ├── worldbeta.js

去掉其中beta部分,然后再拷贝到dest中。

Templates

Templates specified using <% %> delimiters will be automatically expanded when tasks read them from the config。Templates 是由<% %>符号指定的。
grunt and its methods are available inside templates, eg. <%= grunt.template.today('yyyy-mm-dd') %>.

Given the sample concat task configuration below, running grunt concat:samplewill generate a file named build/abcde.js by concatenating the banner /* abcde */ with all files matching foo/*.js + bar/*.js + baz/*.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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'],
});

Importing External Data

In the following Gruntfile, project metadata is imported into the Grunt config from a package.json file, and the grunt-contrib-uglify plugin uglify task is configured to minify a source file and generate a banner comment dynamically using that metadata.

Grunt has grunt.file.readJSON and grunt.file.readYAML methods for importing JSON and YAML data. 注意,这两个方法可以分别从jsonYAML文件中读取配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
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'
}
}
});