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 库。典型的文件夹结构包括以下文件夹:srcdisttestsrc 文件夹(有时称为 app)包含你编写的库的源代码。dist 文件夹(有时称为 build)包含发行版,即源代码的压缩版本。压缩文件是指删除了所有不必要的字符,如空格、换行、注释,同时不影响源代码的功能。压缩的源代码对项目用户特别有用,因为它减少了需要传输的数据量。最后,test 文件夹包含用于测试项目的代码。在接下来的章节中创建 Gruntfile 配置时,将使用这种设置。

在开发库和发布新版本的过程中,有一些需要定期执行的任务。例如,你可能想确保你编写的代码遵循最佳实践,或者确保你编写的代码不会导致意外行为。为此,你可以使用一个名为 JSHint 的工具。Grunt 有一个官方插件叫 grunt-contrib-jshint,我们将在这个示例中采用。特别是,你可能想确保在修改代码时不会破坏任何规则或最佳实践。因此,一个好的策略是在每次更改时检查代码。为此,我们将介绍一个名为 grunt-contrib-watch 的 Grunt 插件。后者在添加、更改或删除文件时运行预定义的任务,如 grunt-contrib-jshint

仅仅检查源代码是否遵循最佳实践是不够的,无法保证其稳定性和没有 bug。要创建一个健壮的项目,你需要对其进行测试。有几个可以采用的库,如 QUnitJasmine。在本指南中,我们描述如何配置 QUnit,特别是 grunt-contrib-qunit,来测试你的代码。

在分发你的作品时,你希望提供一个尽可能小的版本。要创建压缩版本,你需要像 grunt-contrib-uglify 这样的 Grunt 插件。此外,除非你开发的项目非常小,否则你可能已经将代码拆分为多个文件。虽然这对开发者来说是一个好的做法,但用户希望只包含一个文件。因此,在压缩代码之前,你应该将源文件连接起来创建一个单一文件。为实现这一目标,你需要像 grunt-contrib-concat 这样的 Grunt 插件。

总结一下,在本指南中,我们将使用以下五个 Grunt 插件:

如果你对最终结果感到好奇,完整的 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-uglifydist/ 中创建一个文件,该文件包含压缩 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 文件时立即运行 jshintqunit 任务。当它检测到指定的文件发生变化(这里,我们使用与 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']);

};