Visual Studio - 用于 Web 开发的新式工具: Grunt 和 Gulp
作者 Adam Tuliper | 2015 年 12 月
现代 Web 开发者可以使用的工具有很多。JavaScript 任务运行程序 Grunt 和 Gulp 是目前 Web 项目中最常用的两种工具。如果您从未使用 JavaScript 运行任务,或者您习惯使用普通的 Visual Studio 进行 Web 开发,则会觉得这是个陌生概念,但您有充分的理由试一试。JavaScript 任务运行程序在浏览器外部工作,通常在命令行处使用 Node.js。这样,您便可以轻松运行前端开发相关任务,包括缩小、串联多个文件、确定脚本相关性并按正确顺序向 HTML 页面插入脚本引用、创建单元测试工具、处理前端生成脚本(如 TypeScript 或 CoffeeScript 等)。
使用哪一个?Grunt 还是 Gulp?
主要是根据个人和项目偏好来选择任务运行程序,除非您想使用的插件只支持特定的任务运行程序。两者的主要区别在于,Grunt 是由 JSON 配置设置驱动,并且每个 Grunt 任务通常必须创建中间文件将结果传递给其他任务;而 Gulp 则是由可执行的 JavaScript 代码驱动(也就是说,不只是由 JSON 驱动),并能将一个任务的结果流式传输到下一个任务,而无需使用临时文件。Gulp 是冉冉升起的新星,因此,您会发现许多新项目都在使用它。Grunt 依然受到许多众所周知的程序(例如,用它来生成 jQuery 的 jQuery)支持。Grunt 和 Gulp 均通过插件工作,这些插件是您安装用来处理特定任务的模块。可用插件的生态系统十分庞大。通常情况下,您会发现任务包同时支持 Grunt 和 Gulp,这再次说明使用哪一个任务运行程序完全是您的个人选择。
安装和使用 Grunt
Grunt 和 Gulp 的安装程序是节点包管理器 (npm),我曾在 10 月发表的文章 (msdn.com/magazine/mt573714) 中简单介绍过它。Grunt 的安装命令实际分为两个部分。第一部分是 Grunt 命令行接口的一次性安装。第二部分是将 Grunt 安装至项目文件夹。通过这两部分安装,您可以在系统中使用 Grunt 的多个版本,并从任意路径使用 Grunt 命令行接口。
#only do this once to globally install the grunt command line runner
npm install –g grunt-cli
#run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like grunt and the grunt plug-ins, similar to bower.json (see the last article)
npm init
#install grunt as a dev dependency into your project (again see last article)
#we will still need a gruntfile.js though to configure grunt
npm install grunt –save-dev
Grunt 配置
Grunt 配置文件只是具有包装器函数的 JavaScript 文件,其中包含配置、插件加载和任务定义(如图 1所示)。
图 1:Grunt 配置文件
module.exports = function (grunt) {
// Where does uglify come from? Installing it via:
// npm install grunt-contrib-uglify --save-dev
grunt.initConfig({
uglify: {
my_target: {
files: {
"dest/output.min.js": "*.js"
}
}
}
});
// Warning: make sure you load your tasks from the
// packages you"ve installed!
grunt.loadNpmTasks("grunt-contrib-uglify");
// When running Grunt at cmd line with no params,
// you need a default task registered, so use uglify
grunt.registerTask("default", ["uglify"]);
// You can include custom code right inside your own task,
// as well as use the above plug-ins
grunt.registerTask("customtask", function () {
console.log("
Running a custom task");
});
};
只需调用以下内容,即可在命令行处运行图 1 中的任务:
#no params means choose the "default" task (which happens to be uglify)
grunt
#or you can specify a particular task by name
grunt customtask
然后,您会在 wwwroot/output-min.js 处获得丑化(缩小)的串联结果。如果您已使用 ASP.NET 缩小和捆绑,则会发现此过程的不同之处,即它不是只能用于运行应用或编译,您可以为任务选择其他更多选项(如丑化)。在我看来,我认为此工作流更加简洁明了、易于理解。
您可以使用 Grunt 将任务串联在一起,让它们彼此依赖。这些任务会同步运行,所以必须先完成一个任务,才能移至下一个任务。
#Specify uglify must run first and then concat. Because grunt works off
#temp files, many tasks will need to wait until a prior one is done
grunt.registerTask("default", ["uglify", "concat"]);
安装和使用 Gulp
Gulp 的安装与 Grunt 类似。我将更详细地介绍 Gulp,但请注意,您可以使用任一任务运行程序执行类似的操作,我只是不想太过重复罢了。Gulp 可以进行全局安装,以便您能在系统上从任意路径使用它;也可以进行本地安装,安装至特定项目版本的项目文件夹中。如果全局安装的 Gulp 发现本地项目中安装的 Gulp,则前者会将控制权移交给后者,从而遵循 Gulp 的项目版本。
#Only do this once to globally install gulp
npm install –g gulp
#Run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like gulp and the gulp plug-ins
npm init
#Install gulp as a dev dependency into your project
#we will still need a gulpfile.js to configure gulp
npm install gulp --save-dev
Gulp 配置和 API
Gulp 和 Grunt 配置的差异显著。gulpfile.js 配置文件通常具有图 2 中所示的结构,包含插件加载和任务定义的“要求”。请注意,我在这里使用的不是 JSON 配置设置;相反,任务是由代码驱动。
图 2:Gulp 配置文件
// All the "requires" to load your
// various plug-ins and gulp itself
var gulp = require("gulp");
var concat = require("gulp-concat");
// A custom task, run via: gulp customtask
gulp.task("customtask", function(){
// Some custom task
});
// Define a default task, run simply via: gulp
gulp.task("default", function () {
gulp.src("./lib/scripts/*.js")
.pipe(concat("all-scripts.js"))
.pipe(gulp.dest("./wwwroot/scripts"));
});
Gulp 通过以下多个密钥 API 和概念工作:src、dest、管道、任务和 glob。gulp.src API 指示 Gulp 开启哪些文件进行处理,然后这些文件通常会通过管道传送到其他一些函数,而不是所创建的临时文件。这就是与 Grunt 的关键差别。下面展示了一些无需通过管道传送结果的 gulp.src 基本示例,我将对此进行简单介绍。此 API 调用将所谓的 glob 作为参数进行提取。一般来说,glob 是您可以进入的模式(类似于正则表达式)。例如,指定一个或多个文件的路径(若要详细了解 glob,请访问github.com/isaacs/node-glob):
#Tell gulp about some files to work with
gulp.src("./input1.js");
#This will represent every html file in the wwwroot folder
gulp.src("./wwwroot/*.html")
#You can specify multiple expressions
gulp.src(["./app/**/*.js", "./app/*.css"]
如您所想,dest(目标)API 指定目标并提取 glob。由于 glob 能够灵活定义路径,因此您可以输出各个文件或输出到文件夹中:
#Tell dest you"ll be using a file as your output
gulp.dest ("./myfile.js");
#Or maybe you"ll write out results to a folder
gulp.dest ("./wwwroot");
Gulp 中的任务就是您编写用来执行特定操作的代码。任务格式十分简单,但任务的使用方式有多种。最直接的方式是使用默认任务以及一个或多个其他任务:
gulp.task("customtask", function(){
// Some custom task to ex. read files, add headers, concat
});
gulp.task("default", function () {
// Some default task that runs when you call gulp with no params
});
任务可以并行执行,也可以彼此依赖。如果您不关心顺序,就可以将它们串联在一起,如下所示:
gulp.task("default", ["concatjs", "compileLess"], function(){});
此示例定义了默认任务,只单独运行各个任务,以串联 JavaScript 文件和编译 LESS 文件(假设此处命名的任务中包含代码)。如果要求规定必须先完成一个任务,然后再执行另一个任务,那么您需要让任务彼此依赖,然后再运行多个任务。在以下代码中,默认任务先等待串联完成,进而等待丑化完成:
gulp.task("default", ["concat"]);
gulp.task("concat", ["uglify"], function(){
// Concat
});
gulp.task("uglify", function(){
// Uglify
});
管道 API 使用 Node.js 流 API 通过管道将一个函数的结果传送到另一个函数。工作流通常为:读取 src,通过管道发送给任务,通过管道将结果发送到目标。这一完整的 gulpfile.js 示例读取 /scripts 中的所有 JavaScript 文件,将它们串联到一个文件中,然后将输出写入另一个文件夹:
// Define plug-ins – must first run: npm install gulp-concat --save-dev
var gulp = require("gulp");
var concat = require("gulp-concat");
gulp.task("default", function () {
#Get all .js files in /scripts, pipe to concatenate, and write to folder
gulp.src("./lib/scripts/*.js")
.pipe(concat("all-scripts.js"))
.pipe(gulp.dest("./wwwroot/scripts"));
}
下面讲一个现实生活中的示例。您经常想串联文件和/或在源代码文件中添加信息标头。只需几步操作,即可轻松完成。您可以向网站的根添加几个文件(此任务也全都通过代码完成,请参阅 gulp-header 文档)。首先,创建名为 copyright.txt 的文件,其中包含要添加的标头,如下所示:
/*
MyWebSite Version <%= version %>
https://twitter.com/adamtuliper
Copyright 2015, licensing, etc
*/
接下来,创建名为 version.txt 的文件,其中包含当前版本号(也有可以实现版本号递增的插件,如 gulp-bump 和 grunt-bump):
1.0.0
此时,在项目根中安装 gulp-header 和 gulp-concat 插件:
npm install gulp-concat gulp-header --save-dev
您也可以将它们手动添加到 package.json 文件中,并且允许 Visual Studio 为您执行包还原。
最后,您只需使用 gulpfile.js 指示 Gulp 要执行的操作(如图 3 所示)。如果您不想将所有脚本串联在一起,只想向每个文件添加标头,则只需注释禁止管道(串联)行即可。非常简单,是不是?
图 3:Gulpfile.js
var gulp = require("gulp");
var fs = require("fs");
var concat = require("gulp-concat");
var header = require("gulp-header");
// Read *.js, concat to one file, write headers, output to /processed
gulp.task("concat-header", function () {
var appVersion = fs.readFileSync("version.txt");
var copyright =fs.readFileSync("copyright.txt");
gulp.src("./scripts/*.js")
.pipe(concat("all-scripts.js"))
.pipe(header(copyright, {version: appVersion}))
.pipe(gulp.dest("./scripts/processed"));
});
然后,您只需通过以下命令运行此任务即可。瞧,您已经串联了所有 .js 文件,添加了自定义标头,并将输出写入了 ./scripts/processed 文件夹:
gulp concat-header
任务运行程序资源管理器
Visual Studio 通过任务运行程序资源管理器为 Gulp 和 Grunt 提供支持。Visual Studio 2015 中包含此资源管理器(作为一项 Visual Studio 扩展)。您可以在“查看 | 其他 Windows | 任务运行程序资源管理器”中找到它。任务运行程序资源管理器非常棒,因为它会检测项目中是否有 gulpfile.js 或 gruntfile.js,解析任务,然后提供 UI 执行所发现的任务(如图 4 所示)。此外,当项目中发生预定义操作时,您可以定义要运行的任务。接下来,我将介绍这一点,因为 ASP.NET 5 在它的默认模板中使用此功能。只需右键单击任务进行执行,或者将它与特定操作绑定(例如,对项目 Open 执行任务)。
图 4:同时显示 Grunt 和 Gulp 任务及选项的任务运行程序资源管理器
如图 5 所示,当您使用 Gulp 看不到想要突出显示的 Grunt 任务时,Grunt 提供可用选项,特别是强制选项(用于忽略警告)和详述选项(左上角的 F 和 V)。这些只是传递到 Grunt 命令行的参数。任务运行程序资源管理器的主要优点是,可以显示传递到 Grunt 和 Gulp 命令行的命令(在任务输出窗口中),这就让后台运行情况不再神秘。
图 5:其他 Grunt 选项和命令行
Gulp 和 ASP.NET 5
Visual Studio 2015 中的 ASP.NET 5 模板使用 Gulp,这些模板将 Gulp 安装到项目的 node_components 文件夹,可供项目随时使用。您当然仍能在 ASP.NET 5 项目中使用 Grunt;只需务必按照以下方式将它安装到项目中即可:通过 npm,或将它添加到 devDependencies 内的 packages.json 中,并允许 Visual Studio 的自动包还原功能运转起来。我想强调的是: 您可以通过命令行或在 Visual Studio 内部(以您的偏好为准)做到这一切。
截至本文撰写之时,当前的 ASP.NET 5 模板包含多个任务,可缩小和串联 .css 和 .js 文件。在旧版 ASP.NET 中,这些任务是在运行时通过已编译的代码进行处理,但这并不是完成此类任务的理想位置和时间。如图 6 所示,命名为 clean 和 min 的任务调用它们的 css 和 js 方法,以便缩小这些文件或清理之前缩小的文件。
图 6:ASP.NET 5 预览模板中开箱即用的任务
再例如,下面的 Gruntfile.js 行展示了如何同时运行多个任务:
gulp.task("clean", ["clean:js", "clean:css"]);
您可以视需要选择将 Grunt 和 Gulp 任务绑定到 Visual Studio 中的 4 个不同操作。使用 MSBuild 时,惯常做法是定义预生成和生成后的命令行,以便执行各种任务。借助任务运行程序资源管理器,您可以定义“生成前”、“生成后”、“清理”和“项目 Open”事件来执行这些任务。这样做只会向 gulpfile.js 或 gruntfile.js 文件添加注释,并不影响执行,但会被任务运行程序资源管理器查找。若要了解 ASP.NET 5 gulpfile.js 中的“清理”绑定关系,请查看文件顶部的这一行内容:
// <binding Clean="clean" />
这就是跟踪事件所需完成的一切。
总结
Grunt 和 Gulp 都非常适合添加到您的 Web 工具库中。两种任务运行程序均受到大力支持,且可用插件的生态环境十分庞大。每个 Web 项目都能受益于这两种任务运行程序。有关详细信息,请务必查阅以下内容:
- Grunt“入门”文档 (bit.ly/1dvvDWq)
- Gulp 方案 (bit.ly/1L8SkUC)
- 包管理和工作流自动化: 计算机包管理器 (bit.ly/1FLwGW8)
- Node.js 主控模块和包 (bit.ly/1N8UKon)
Adam 的修理厂 (bit.ly/1NSAYxK)
- *
Adam Tuliper 是生活在阳光明媚的南加州的一位 Microsoft 资深技术传播者。他是 Web 开发者、游戏开发者、Pluralsight 作者以及全面技术的爱好者。通过 Twitter @AdamTuliper 或“Adam 的修理厂”博客 (bit.ly/1NSAYxK) 与他取得联系。