Configuring tasks

配置任务

本指南解释了如何使用 Gruntfile 为您的项目配置任务。如果您不知道什么是 Gruntfile,请阅读 新手入门 指南并查看 示例 Gruntfile

Grunt 配置

任务配置是通过 grunt.initConfig 方法在您的 Gruntfile 中指定的。这个配置主要位于任务名称属性下,但可以包含任何任意数据。只要属性不与任务所需的属性冲突,它们就会被忽略。

另外,因为这是 JavaScript,您不仅限于 JSON;您可以在此处使用任何有效的 JavaScript。如果需要,您甚至可以以编程方式生成配置。

grunt.initConfig({
  concat: {
    // concat 任务配置在这里。
  },
  uglify: {
    // uglify 任务配置在这里。
  },
  // 任意非任务特定属性
  my_property: 'whatever',
  my_src_files: ['foo/*.js', 'bar/*.js'],
});

任务配置和目标

当运行一个任务时,Grunt 会在同名属性下查找其配置。多任务可以有多个配置,使用任意命名的"目标"。在下面的示例中,concat 任务有 foobar 目标,而 uglify 任务只有一个 bar 目标。

grunt.initConfig({
  concat: {
    foo: {
      // concat 任务 "foo" 目标的选项和文件在这里。
    },
    bar: {
      // concat 任务 "bar" 目标的选项和文件在这里。
    },
  },
  uglify: {
    bar: {
      // uglify 任务 "bar" 目标的选项和文件在这里。
    },
  },
});

指定任务和目标,如 grunt concat:foogrunt concat:bar 将只处理指定目标的配置,而运行 grunt concat 将遍历所有目标,依次处理每个目标。注意,如果任务已使用 grunt.task.renameTask 重命名,Grunt 将在配置对象中查找任务名称的属性。

选项

在任务配置中,可以指定 options 属性以覆盖内置默认值。此外,每个目标可以有特定于该目标的 options 属性。目标级别的选项将覆盖任务级别的选项。

options 对象是可选的,如果不需要可以省略。

grunt.initConfig({
  concat: {
    options: {
      // 任务级别选项可以在这里,覆盖任务默认值。
    },
    foo: {
      options: {
        // "foo" 目标选项可以在这里,覆盖任务级别选项。
      },
    },
    bar: {
      // 未指定选项;此目标将使用任务级别选项。
    },
  },
});

文件

因为大多数任务执行文件操作,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'},
      ],
    },
  },
});

旧格式

目标作为目标文件格式是在多任务和目标存在之前的遗留格式,其中目标文件路径实际上是目标名称。不幸的是,因为目标名称是文件路径,运行 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/'],     // 原始 CSS 模板的父文件夹
        src: '**/*.css',             // 收集父文件夹中的所有 `*.css` 文件(及其子文件夹)
        dest: 'src/css/',            // 将收集的 `*.css` 文件存储在您的 `src/css/` 文件夹中
        filter: function (dest) {    // 在这个实例中,`dest` 是每个匹配的 `src` 的文件路径
          var cwd = this.cwd,        // 配置变量(仅为您的方便记录)
              src = dest.replace(new RegExp('^' + cwd), '');
              dest = grunt.task.current.data.files[0].dest;
          return (!grunt.file.exists(dest + src));    // 仅在目标未被占用时复制 `src` 文件
        }
      }]
    }
  }
});

请注意,上述技术在检查目标是否存在时不考虑 重命名属性

通配符模式

单独指定所有源文件路径通常是不切实际的,因此 Grunt 通过内置的 node-globminimatch 库支持文件名扩展(也称为通配符)。

通配符模式的工作方式类似于 shell 中的文件名扩展。通配符模式可以包含以下特殊字符:

  • * 匹配任意数量的字符,但不匹配 /
  • ? 匹配单个字符,不包括 /
  • ** 匹配任意数量的字符,包括 /,只要模式中的其他部分匹配
  • {} 允许使用逗号分隔的"或"表达式,例如 {a,b} 匹配 ab
  • ! 在模式开头表示否定模式,如果文件匹配后面的模式,则不会被包括

以下是一些示例:

// 匹配所有在 `js/` 目录中的 `.js` 文件
'js/**/*.js'

// 匹配 `js/` 目录中的所有 `.js` 文件,但不包括子目录
'js/*.js'

// 匹配 `js/` 目录中的所有 `.js` 文件,包括子目录,但不包括 `js/vendor/` 目录
['js/**/*.js', '!js/vendor/**']

动态构建文件对象

当您需要更多控制文件映射时,可以使用 expand 选项动态生成文件对象。

基本示例

grunt.initConfig({
  concat: {
    dist: {
      expand: true,     // 启用动态扩展
      cwd: 'src/',      // 源文件的相对路径
      src: ['**/*.js'], // 匹配所有子目录中的 .js 文件
      dest: 'dist/',    // 目标路径前缀
      ext: '.min.js',   // 目标文件扩展名
      extDot: 'first'   // 扩展名添加在文件名的第一个点之前
    }
  }
});

高级示例

grunt.initConfig({
  rename: {
    dist: {
      expand: true,
      cwd: 'src/',
      src: ['**/*.js'],
      dest: 'dist/',
      rename: function(dest, src) {
        return dest + src.replace('.js', '.min.js');
      }
    }
  }
});

模板

Grunt 允许在配置中使用模板。这些模板可以引用其他配置属性,并且可以使用 <% %> 语法。

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  concat: {
    dist: {
      src: ['src/<%= pkg.name %>.js'],
      dest: 'dist/<%= pkg.name %>.js'
    }
  }
});

自定义任务选项

每个任务都可以有自己的选项。这些选项可以在任务级别或目标级别指定。

grunt.initConfig({
  uglify: {
    options: {
      banner: '/* <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    dist: {
      options: {
        compress: true
      },
      files: {
        'dist/app.min.js': ['src/app.js']
      }
    }
  }
});

总结

Grunt 的任务配置非常灵活,支持多种文件处理和选项配置方式。通过理解这些配置技术,您可以创建强大且高效的构建流程。