黑人的命也是命

配置任务

本指南解释了如何使用 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 任务具有 foobar 目标,而 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:foogrunt 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(源-目标)文件映射,提供不同程度的冗长度和控制。任何多任务都将理解以下所有格式,因此请选择最能满足您需求的格式。

所有文件格式都支持 srcdest,但紧凑文件数组格式支持一些额外的属性

  • filter 有效的 fs.Stats 方法名称 或传递匹配的 src 文件路径并返回 truefalse 的函数。查看示例
  • nonull 如果设置为 true,则操作将包括不匹配的模式。结合 grunt 的 --verbose 标志,此选项可以帮助调试文件路径问题。
  • dot 允许模式匹配以句点开头的文件名,即使该模式在该位置没有明确的句点。
  • matchBase 如果设置,则不带斜杠的模式将与路径的基本名称匹配,如果它包含斜杠。例如,a?b 将匹配路径 /xyz/123/acb,但不匹配 /xyz/acb/123
  • expand 处理动态 src-dest 文件映射,有关更多信息,请参阅“动态构建文件对象”
  • 其他属性将作为匹配选项传递到底层库。有关更多选项,请参阅 node-globminimatch 文档。

Grunt 和任务选项之间的区别

大多数任务都执行文件操作,因此 Grunt 提供了一个内置的基础结构来检索任务应该处理的文件。这样做的好处是任务作者不必再次实现此逻辑。为了允许用户指定这些文件,Grunt 提供了 nonullfilter 等选项。

除了要处理的文件之外,每个任务都有其自身的特定需求。任务作者可能希望允许其用户配置一些选项来覆盖默认行为。这些特定于任务的选项不应与前面描述的 Grunt 选项混淆。

为了进一步说明这种差异,让我们看一个使用 grunt-contrib-jshint 的示例

grunt.initConfig({
  jshint: {
    ignore_warning: {
      options: {
        '-W015': true,
      },
      src: 'js/**',
      filter: 'isFile'
    }
  }
});

此配置使用 Grunt 选项 srcfilter 来指定要处理的文件。它还使用 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 函数并返回 truefalse,无论文件是否应该匹配。例如,以下内容将仅清除为空的文件夹

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-globminimatch 库支持文件名扩展(也称为通配)。

虽然这不是关于通配符模式的全面教程,但请知道在文件路径中

  • * 匹配任意数量的字符,但不匹配 /
  • ? 匹配单个字符,但不匹配 /
  • ** 匹配任意数量的字符,包括 /,只要它是路径部分中唯一的内容
  • {} 允许使用逗号分隔的“或”表达式列表
  • 模式开头的 ! 将否定匹配

大多数人需要知道的是,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-globminimatch 文档。

动态构建文件对象

当您要处理许多单独的文件时,可以使用一些附加属性来动态构建文件列表。可以在紧凑文件数组映射格式中指定这些属性。

expand 设置为 true 将启用以下属性

  • cwd 所有 src 匹配都相对于(但不包括)此路径。
  • src 要匹配的模式,相对于 cwd
  • dest 目标路径前缀。
  • ext 在生成的 dest 路径中用此值替换任何现有扩展名。
  • extDot 用于指示指示扩展名的句点所在的位置。可以取 'first'(扩展名从文件名的第一个句点开始)或 'last'(扩展名从最后一个句点开始),默认设置为 'first' [在 0.4.3 中添加]
  • flatten 从生成的 dest 路径中删除所有路径部分。
  • rename 嵌入一个自定义函数,该函数返回一个包含新目标和文件名的字符串。为每个匹配的 src 文件(在扩展名重命名和展平之后)调用此函数。更多信息

在以下示例中,uglify 任务将看到 static_mappingsdynamic_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.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'
    }
  }
});