🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 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)) 与他取得联系。*