# Visual Studio - 用于 Web 开发的新式工具: Grunt 和 Gulp
作者 [Adam Tuliper](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=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](http://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("\r\nRunning 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](http://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 执行任务)。
![](https://box.kancloud.cn/2016-01-08_568f2a7f90a97.png)
图 4:同时显示 Grunt 和 Gulp 任务及选项的任务运行程序资源管理器
如图 5 所示,当您使用 Gulp 看不到想要突出显示的 Grunt 任务时,Grunt 提供可用选项,特别是强制选项(用于忽略警告)和详述选项(左上角的 F 和 V)。这些只是传递到 Grunt 命令行的参数。任务运行程序资源管理器的主要优点是,可以显示传递到 Grunt 和 Gulp 命令行的命令(在任务输出窗口中),这就让后台运行情况不再神秘。
![](https://box.kancloud.cn/2016-01-08_568f2a7fc09c5.png)
图 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 方法,以便缩小这些文件或清理之前缩小的文件。
![](https://box.kancloud.cn/2016-01-08_568f2a7fe0cfb.png)
图 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](http://bit.ly/1dvvDWq))
* Gulp 方案 ([bit.ly/1L8SkUC](http://bit.ly/1L8SkUC))
* 包管理和工作流自动化: 计算机包管理器 ([bit.ly/1FLwGW8](http://bit.ly/1FLwGW8))
* Node.js 主控模块和包 ([bit.ly/1N8UKon](http://bit.ly/1N8UKon))
* Adam 的修理厂 ([bit.ly/1NSAYxK](http://bit.ly/1NSAYxK))
* * *
Adam Tuliper *是生活在阳光明媚的南加州的一位 Microsoft 资深技术传播者。他是 Web 开发者、游戏开发者、Pluralsight 作者以及全面技术的爱好者。通过 Twitter [@AdamTuliper](https://twitter.com/@AdamTuliper) 或“Adam 的修理厂”博客 ([bit.ly/1NSAYxK](http://bit.ly/1NSAYxK)) 与他取得联系。*
- 介绍
- Visual Studio - 用于 Web 开发的新式工具: Grunt 和 Gulp
- 新员工 - 放长钱钓大鱼
- Microsoft Azure - Azure Service Fabric 和微服务体系结构
- 数据点 - Aurelia 与 DocumentDB 结合: 结合之旅(第 2 部分)
- 游戏开发 - Babylon.js: 构建 Web 基本游戏
- 测试运行 - 面向 .NET 开发者的 Spark 简介
- Xamarin - 使用 Xamarin.Forms 构建跨平台用户体验
- 孜孜不倦的程序员 - 如何成为 MEAN: 快速输入
- Microsoft Azure - Azure、Web API 和 Redis 如何有助于加快数据交付
- 必备 .NET - 设计 C# 7
- 新式应用 - 需要了解的 Windows 10 应用开发概念
- 别让我打开话匣子 - 重构高等教育
- 编者注 - 再见 Kenny