Gulp简介

分享于 

48分钟阅读

Web开发

  繁體

内容

简介

最后 2015已经到达了。这是一年内将更加精美的技术进展。 也是 Visual Studio 2015将要发布的年份。 另一年,另一个版本的Visual Studio 大多数。 但有更多的behind ! 首先,它将是. NET 编译平台("") 附带的第一个版本。 这不仅包括 C# ( 和 VB ) 编程语言的新版本,而且还包括基于compiler-as-a-service概念的创新工具。 然后这也会影响其他产品。

这些产品之一是 ASP.NET。 这个框架已经从底层编写出来了。 它已经与 System.Web 依赖关系进行了分解,并且准备就绪而且没有大量的闪烁。 它还具有compile-as-you-go功能,没有使用二进制文件。 这一切都归功于Roslyn和在改进web框架方面的无数努力。 无论你想要做 web to,MVC还是只是轻量级的Web API - 这个新 ASP.NET 当然是爱的。

这种改变不仅对后端开发有影响,而且对前端爱好者也有影响。 在 ASP.NET MVC 4中,团队包含了诸如捆绑。 这个想法很简单:把( 缩小,如果合适的话) 内容与某些规则结合起来。 在高级语言中使用特殊的文件变得更加复杂,比如SASS或者 app。 通常的解决方案是在源代码改变时编译到较低的变体( CSS或者 JavaScript )。 编译后的文件随后触发了分布式分发包的更改。

几年后,这似乎仍然是工作,但是它远远远远远远远远超过了纯前端web开发人员的工作。 在这里我们构建一个构建过程,它基本上像以前的makefile一样行为。 我们声明了我们拥有的和我们想要做的。 最后我们得到了一个可以部署的结果。 一旦达到部署,我们需要运行makefile来获得更新的版本。 没有智能程序能随时检查变化。 不需要文件监视器。

因此 ASP.NET 5 (。例如。MVC 6,目前被称为"vnext") 将删除捆绑和相关功能,并依赖于前端构建流程。 可以使用 makefile,但是为什么不马上开始与酷的孩子一起玩? 因此 ASP.NET 团队包括两个重要的构建系统 out-of-the-box ( IDE集成): 建立的Grunt任务 runner 及其新的竞争对手 Gulp。 在这篇文章中,我们将仔细查看一下吞咽情况。 文章本身由 ASP.NET 6驱动,但不需要,也不需要任何 ASP.NET 6特性的例子或者用法。 每个例子都以 static 文件形式给出。

背景

在过去的70年里,计算方面发生了很多变化。 编程语言不断发展,硬件改进,一切都得到了连接。 提高软件开发效率的最重要的触发器之一是构建自动化。 它的思想是拥有几个。更多或者 LESS 独立的源文件,这些文件必须构建并链接在一起。 独立性很重要,因为建筑可以能需要一些时间,因这里有利于将构建过程减少到这些文件。

整个描述似乎是自动化的理想案例。 我们应该有一个程序来检查我们,对吧? 最初编写了一组脚本来实现分离的集成构建的目标。 但是,由于可以用性的思想引起的聪明人员进一步获得了概念并创建了一个名为 make的系统。 它基于依赖关系的概念。 目标( 文件) 有一些依赖关系。 这些依赖关系可能又有一些依赖关系。 为了解决依赖项,必须遵循规则。 这里规则可能涉及修改时间和更多的比较。

Make是相当好的,它是超越 C/C++ 或者Fortran编译的工具。 它对于web开发也非常有用。 但是它高度依赖于其他脚本或者可执行文件。 那么为什么不在节点上构建,以 JavaScript ( 或者网络) 开始? 最大的优点是同构结构。 人们不会混合语言,构建依赖会立即减少。 使它也有吸引力的是包管理器 npm,它表示一个分离的源代码包管理器。 所以我们有一个清晰的分离。

既然我们已经讨论了需要节点的专门构建系统,那么我们需要引入Grunt任务 runner。 Grunt是第一个在节点之上构建自动化系统,它可能是最知名和最有名的前端构建工具之一。 我们为什么要考虑在其他工具上 Grunt?

的优点

Grunt将自己描述为任务 runner。 显然,任务的概念似乎是很明显的。 我们基本上将构建过程分组到任务中,然后可以将它们链接到( 窗体依赖项) 或者单独执行。 主要的想法是只使用插件。 任何可能是代码或者某些脚本形式的东西都必须被插件覆盖。

Grunt还带有一个漂亮的logo,如下所示:

Grunt Task Runner

插件的需求不仅完全忽略了脚本,而且强调了配置级别。 Grunt被认为是Configuration-over-scripting构建自动化系统。 它的插件是专门的工具,必须紧密地适应Grunt的生态系统。 这听起来不错,但是(。类似用法,一致性)的主要原则仍然是插件作者。 然而,配置方面似乎给了我们一个共同点,这绝对是一个好东西。

使用Grunt任务 runner的基本构建过程将在下一个映像中。 我们可以看到每个任务都从磁盘开始,并在磁盘上结束。 没有办法在内存中继续处理源代码。 因此,中间( 临时) 目录是必需的。

Grunt Scheme

配置通常是用简单的JavaScript对象提供的。 这不一定是JSON对象( JSON对象的语法比普通的JavaScript对象要严格得多)。 显然,这种配置将由Grunt来考虑。 Grunt需要某些部分来识别插件。 这些插件将用提供的选项调用。

有许多插件供你使用。 这也是必需的。我们已经看到,sdl需要一个脚本来适应它的生态系统。 因此,可以从Grunt中调用的任何东西都是。 没有很多插件,Grunt是相当无用的。 Grunt插件eco系统的有趣之处在于许多插件的丰富性。 只有几个不仅仅是一件事情的插件。 另一方面也可以理解,因为插件是需要的,而且比多个轻量级的插件更容易。 这也是基于文件模型的局限性的经典方法。

Grunt最大的优势是它庞大的社区和庞大的插件库。 已经建立的产品斗争肯定是很难的。 所以需要一些不同的东西,这不仅仅是一些更好的事情,而且它的核心不同。

有关Grunt的更多信息可以在的官方主页上找到。

:为什么要吞咽

Grunt是比赛的对手。 因此,吞咽重点关注几个关键点: 首先,速度。速度是两种风格。 我们不仅拥有卓越的构建系统( 通常并不重要),而且还拥有卓越的开发速度( 那就更重要了)。 为什么构建速度优越? 我们将看到with使用流,它将保持内存中的工作版本,直到我们决定在硬盘上创建版本。 简单CS101告诉我们,计算机内存的延迟和传输速率比通常的硬盘要好。

第二种风格更有争议,但更重要的是。 通过允许任何代码运行,吞咽器可以完全消除插件。 当然,有 plugins ( 它们确实是非常重要和有用的)的插件,但它们不是强制的。 因此,吞咽不需要相同数量的插件提供 Grunt。 我们通常可以在非常特殊的事物中进行 hack,而不是搜索( 或者写)。 Gulp使用 code-over-configuration,这对大多数开发人员来说可能更好。 配置需要文档,编码需要技能。 我们已经拥有了后者,因此我们应该依赖它。

回到keypoints的gulp。 流是通过一个流畅的API集中的,这与 shell的工作非常相似。 我们只是把一个计算的结果传给下一个。 每个计算都使用虚拟文件系统( 称为乙烯基),它是用JavaScript编写的,并且应该永远不会保留内存。

吞咽非常严重,因此会产生以下的:

Gulp Streaming Task

流式概念是Gulp的核心。 其他的概念基本上都是从。 因此,需要在本文中花费大量的时间来讨论它。 如前所述,我们不生成临时文件。 文件的工作副本存储在内存中。 它也被改变了。 这是强大的,因为它不仅给了我们更高的修改率,而且还能够链接命令的能力。

剩下的是面向功能的框架,带有直接的API,可以将( 磁盘上)的数量减少到最小的( 通常为零)。 下图显示了使用Gulp构建过程的基本方案。 对前面显示的Grunt图像所做的更改是至关重要的。 子任务不直接输出某些内容,而是返回一个已经修改的流。 这里流可以与其他子任务的( 直接提供)。

Gulp Scheme

实际上,API减少到了几个方法。 我们需要一个定义任务的方法,它包括潜在的依赖性和更多的。 我们还需要一种方法将现有文件转换为虚拟文件,换句话说,将文件内容加载到内存中,或者将文件转换。 我们还需要一个方法来将当前的虚拟文件转储到磁盘,并将真实文件写到。 我们也可以能希望直接在API中像文件系统观察器一样,但是这是或者是 LESS 提供的。

当然,给定的List 不是虚构的,而是在实践中存在的。 以下方法位于 Gulp API的中心:

  • 使用 gulp.task(name, fn) 定义任务
  • 使用 gulp.src(glob) 将源转换为流
  • 转储流 gulp.dest(folder)
  • 安装文件系统观察器 gulp.watch(glob, fn)

使用吞咽的构建过程的描述称为 gulpfile。 它是纯( 当然是有效的) JavaScript代码。 代码通过 node.js 执行,这意味着在这里,包装器基本上没有魔力。 然而,我们并不直接通过节点执行 gulpfiles,而是通过一个叫做gulp的自定义可执行文件来执行。 这很有用,因为它允许我们在不改变gulpfile的情况下运行任何任务。 在gulpfile中,我们可以自由地使用任何需要的节点模块。 我们主要对Gulp插件感兴趣,它共享一个简单的概念: 它们都是基本的 !

基本插件或者代码只做一件事,但这一件事真。 它接受任意的输入,然后执行它的工作并返回一些输出,这样另一个基本插件就可以完成一些工作。 这是个基于流的小盒子。

这都导致了一些直接的结果。 Gulpfiles通常更小,更容易阅读,至少对于具有JavaScript或者通用编程 background的人来说。 让我们来看看一个例子。

首先是Grunt配置:

grunt.initConfig({
 less: {
 development: {
 files: {
 "build/tmp/app.css": "assets/app.less" }
 }
 },
 autoprefixer: {
 options: {
 browsers: ['last 2 version', 'ie 8', 'ie 9']
 },
 multiple_files: {
 expand: true,
 flatten: true,
 src: 'build/tmp/app.css',
 dest: 'build/' }
 }
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.registerTask('css', ['less', 'autoprefixer']);

相反,相同的代码可以用Gulp表示,如下所示:

var gulp = require('gulp'),
 less = require('gulp-less'),
 autoprefix = require('gulp-autoprefixer');
gulp.task('css', function() {
 gulp.src('assets/app.less')
. pipe(less())
. pipe(autoprefix('last 2 version', 'ie 8', 'ie 9'))
. pipe(gulp.dest('build'));
});

当然,这只是一个例子,我们不应该过分依赖代码比较。 他们会误导我们,他们会告诉我们错误的课程。 比较可以读性也是一个 false 朋友,因为这个例子比较小且实用的配置更大。 不要再进一步了解,让我们从吞吐开始看看这里是什么。

启动

在接下来的小节中,我们将学习与掌握吞咽有关的一切。 我们将安装它,创建第一个gulpfile和学习任务,插件和重要的主题,如 Pattern 和手表。

安装

在使用Gulp之前,我们应该先安装它。 这里我们假设,node和npm都已经安装和安装。 现在,我们只需要全局安装 Gulp,这将启用 gulp 命令。

我们运行( 使用管理权限,换句话说,使用 sudo 或者提升的shell ):

npm install -g gulp

-g 标志将告诉 npm 全局安装 Gulp。 但是,全局安装只对 gulp 命令是必需的。 在任何项目中使用gulpfile是不够的。 使用gulpfile我们需要一个项目目录,这意味着目录是npm包文件( package.json )的root。 假设我们想创建这样一个 file:

npm init
npm install --save-dev gulp

这里我们初始化了一个基于npm的项目目录( 这将是当前目录,所以注意你执行命令的位置)。 我们还安装了一个项目本地版本的,已经添加到项目( --save-dev )的依赖项。 后一个很重要,因为它允许我们忽略来自我们的VCS的依赖项,并在以后安装 npm install 时。 这就是我们通常想要的。

现在我们可以走了 ! 但是等一下。为什么我们需要另一个本地版本? 原因很简单:每个项目都有一个本地版本,我们确保每个项目都有精确的版本。 package.json 不仅具有 List 依赖项名称,而且还包含依赖项版本。 当然可以允许更现代版本的软件包,但在一般的软件包中,可以确切地指定版本。 这将降低面临重大更改的风险。

吞咽器知道这种方法,因此很难在同一台机器上允许它的自身的特定版本。 这是通过项目本地版本实现的。 最后,我们失去了一些磁盘空间,但是我们获得了在另一台机器上触发来源和安装声明的( 本地)。

压缩JavaScript文件

让 我们 从 一个 示例 开始。 考虑以下目录结构:

. Project Directory
|--. bin
|--. node_modules
|--. src
| |--. css
| |--. html
| |--. js

我们在项目目录中有 gulpfile.js。 我们的目标是生成可能部署在网络上的输出。 因此,我们需要一些任务 比如 来最小化( 并结合) 某些JavaScript文件。 我们假设src目录包含所有原始源代码,bin目录将是我们构建过程的目标。

让我们看看 gulpfile,它采用单个JavaScript文件( 称为 app.js ),最小化它并将它的存储在bin文件夹( 没有子目录,直接在那里) 中。

var gulp = require('gulp'),
 uglify = require('gulp-uglify');
gulp.task('minify-js', function() {
 gulp.src('src/js/app.js')
. pipe(uglify())
. pipe(gulp.dest('bin'));
});

这里发生了什么我们从两个模块开始。 首先是吞咽自己。 很明显这很重要。 第二个是 gulp-uglify,它实际上是一个 JavaScript。 这里有一个构造函数,这意味着我们需要在某个点调用 uglify 函数。 这是一个典型的Pattern,带有吞咽插件,我们将看到。

使用 task 方法创建一个名为"缩小 js"的新任务。 这里 NAME 是任意的,但应该是描述指定任务的好匹配。 任务本身是以回调形式给出的。 这里我们创建一个新的流,它表示 src/js/app.js, 中文件的内容,然后管道被管道传递到 uglify 构造函数创建的另一个函数。 最后通过调用 dest 方法以表示目标目录的参数将修改后的流传递给已经创建的函数。

合并和压缩CSS文件

让我们重写上一个示例,对CSS做同样的事情,只不过是一个CSS文件,但是一组。 通过连接它们,我们可以将多个CSS文件组合在一个。 因此我们将保存一些请求。 客户端优化是让客户满意和获得更多业务价值的极好技术。 但是,CSS文件的顺序非常重要,因为某些规则可能会覆盖以前的声明。

而不是提供一个 MATCHES 文件的单个字符串,因这里也可以提供一个以 array 为单位的字符串的List。 我们将使用另一个插件缩小 CSS,因为最小化CSS的规则与压缩JavaScript代码的规则不同。

var gulp = require('gulp'),
 concat = require('gulp-concat'),
 minifyCss = require('gulp-minify-css');
gulp.task('minify-css', function() {
 gulp.src(['src/css/bootstrap.css', 'src/css/jquery.ui.css', 'src/css/all.css'])
. pipe(concat('style.css'))
. pipe(minifyCss())
. pipe(gulp.dest('bin'));
});

基本原则是相同的。 我们设置插件( 在这里我们也使用 concat 插件来合并文件),指定任务并通过管道调用各种构造函数。

也许我们甚至想存储各种版本。 一个是串联输出( style.css ),一个是串联和缩小输出( style.min.css )。 Gulp让我们很容易做到这一点,因为输出也只是另一个管道目标。 因此,我们的方案更改为类似于以下图片的内容:

Gulp Multiple Output Scheme

代码中的情况如何? 我们将使用 gulp-rename 插件来重命名我们的扩展。 但其他的应该是熟悉的:

var gulp = require('gulp'),
 concat = require('gulp-concat'),
 minifyCss = require('gulp-minify-css'),
 rename = require('gulp-rename');
gulp.task('minify-css', function() {
 gulp.src(['src/css/bootstrap.css', 'src/css/jquery.ui.css', 'src/css/all.css'])
. pipe(concat('style.css'))
. pipe(gulp.dest('bin'))
. pipe(minifyCss())
. pipe(rename({ extname: '.min.css' }))
. pipe(gulp.dest('bin'));
});

这里的整个管道概念非常有用,让我们可以轻松地连接子任务。 但是,这也是对实际任务进行仔细查看的好时机,它表示了这些子任务的组合。

任务和依赖项

现在我们已经看到了我们可以做的是,进一步研究任务和依赖关系。 如前所述,我们需要将所有内容。 我们可以有一个任务做一切,但绝对不推荐,不会非常敏捷。 每个任务基本上都是一个构建目标。 以前我们指定了诸如压缩CSS或者压缩JavaScript之类的目标。

每个目标都可以通过 gulp 命令调用。 但是每个目标也可以被视为其他目标的依赖项。 吞咽也知道一个非常特殊的目标,即 default。 默认目标是在调用 gulp 命令时调用的,没有任何附加参数时调用。 最好把它看作是一个"全部制作"-kind指令。 因此,我们通常为所有其他( 子- ) 任务提供依赖。

这些依赖项以 array 形式给出,包含字符串,这些字符串命名了( 依赖项) 中所需的。 下面是 default 任务的一个可能定义:

gulp.task('default', ['clean', 'styles', 'scripts']);

构建 default 任务时,需要运行 clean 任务,然后运行 scripts 任务的styles 和 finally。 如果没有返回流对象,则该订单很重要,即使,可以并行化生成过程。 因这里 clean 任务必须返回流,否则整个系统可以能根据子任务的进度而定义。

让我们用所需的依赖项定义 clean 任务。 这里我们提供了 src 方法的附加参数,提示该目录的内容不应该被读取。 我们唯一关心的是要清理的目录信息。

var gulp = require('gulp'),
 clean = require('gulp-clean');
gulp.task('clean', function() {
 return gulp.src('bin', { read: false })
. pipe(clean());
});

在任何情况下都建议返回流。 有时,省略它可以能有利,但大多数时候这可以能导致未定义的行为。 因此如果我们不关心( 并知道) 应该发生什么,我们要确保一切都按照我们指定的顺序构建。

运行吞咽

现在我们设置一个 gulpfile,我们已经准备好了。 我们要做什么来触发构建? 实际上不太简单。在最简单的情况下,我们只在项目目录中运行以下命令。 Gulp然后尝试在当前目录中查找一个 gulpfile。

gulp

这将触发隐式生成过程。 隐式版本在gulpfile中查找名为 default的任务并运行它。 如果任务不存在( 或者没有找到 gulpfile ),将显示错误消息。 如果脚本包含任何其他错误,也会显示错误消息。

如果我们想要更明确地说,我们可以 NAME 来运行任务。 例如要合并和缩小所有的CSS脚本( 第二个示例),我们将被迫运行:

gulp minify-css

如果我们想运行这个和第一个示例? 运行两个命令,或者输入带有两个参数的gulp 命令,如下所示:

gulp minify-css minify-js

重要的是强调Gulp的本地版本实际上运行。 gulp 命令可以能是全局的,但最终执行的代码始终是本地的。

使用globs选择文件

我们已经看到了两种指定文件的可能性: 作为单个字符串,作为字符串的array。 但是每个字符串实际上都有一个特殊的含义,并用一个特殊的包解析。 实际上,因为它们必须遵循某些规则,所以我们倾向于调用这些字符串。

通常术语匹配指的是基于通配符的Pattern 匹配。 名词"glob"用于引用特定的Pattern,比如"使用 glob *.log to MATCH 所有日志文件"。 它们的符号比 比如。正规表达式。简单,而且没有表达能力。 然而,格式包含所有的特性,以选择和取消选择文件的名称。

Gulp Pattern 中的glob是基于glob包( 这里有 npmjs.com/package/glob。)。 它是常见模式的扩展,并且具有其他正则表达式,如特性。

例如在解析路径部分模式之前,字符串中的支撑节被扩展为一个集合。 支撑节以 { 开始,以 } 结束。 支撑节可以包含任意数量的逗号分隔节。 支撑节也可能包含斜线字符,因此 a{/b/c,bcd} 将扩展到 a/b/cabcd

以下字符在路径部分中具有特殊的神奇含义:

  • *: 在单个路径部分中包含 0个或者更多字符。
  • ?: 单个路径部分中的MATCHES 1字符。
  • [...] 范围:MATCHES的字符范围,类似于 正规表达式 范围。
  • |: 它在一组( 由 (...) 表示) 中分离了各种可能性。

组本身必须具有描述它的用法的前缀。 例如地球仪:

!(pattern1|pattern2|pattern3)

匹配与( pattern1pattern2pattern3 ) 提供的任何模式都不匹配的任何。

此外,以下前缀可以用于组:

  • ?: 它是 MATCHES 零或者一个模式的一个出现。
  • +: 如果至少要匹配一个模式,这很有用。
  • *: 用于对零个或者多个模式进行 MATCH。
  • @: MATCHES 恰好是所提供的模式之一。

特殊情况是双星,由 ** 给出。 如果在路径部分中单独发现这种组合,那么 MATCHES 零或者更多目录和子目录。

论在实践中这意味着什么? 如果我们使用 "js/app.js",那么就完全匹配路径。 如果我们想要除以前给出的所有其他JavaScript文件,我们可以指定 "js/(app.js)"。! 如果我们想从给定目录中所有的应用程序和JavaScript文件,我们可以自由运行 "js/*.+(js|ts)

重要插件

我们已经意识到,吞咽是强大的,一般与特定的插件无关。 然而,插件是非常有用的,让我们的生活更容易。 一般来说,我们将一直喜欢一个专门用于吸取一些必须集成在一起的代码的插件。

插件是通过 npm 安装的,就像吞下自己一样。 它们总是在本地安装。 安装 uglify 插件的示例如下:

npm install --save-dev gulp-uglify

这些插件在使用中往往非常小和简洁。 通常需要知道的一切都是在他们的GitHub存储库( 这是其中大部分插件的宿主) 或者in系统中给出的。 否则,对源代码的简短查看是可以管理的。

在这个部分中,最重要的前端web开发任务插件将会呈现出来。

常规

这里我们将看一些通常在各种任务中使用的插件。 它们对于 CSS。JavaScript或者其他类型的转换很有用。 下面的内容似乎非常有用:

  • gulp-concat ( 我们已经看到了)
  • gulp-clean ( 我们已经看到过了)
  • gulp-notify ( 对于通知非常有用)
  • gulp-livereload ( 稍后将在 GREATER 细节中讨论)

下面的示例使用通知插件在文件合并发生之前和之后输出消息。

var gulp = require('gulp'),
 notify = require('gulp-notify'),
 concat = require('gulp-concat');
gulp.task('concat', function() {
 gulp.src('*.js')
. pipe(notify("Alright, lets merge these files!"))
. pipe(concat('concat.js'))
. pipe(notify("Files successfully merged.. ."))
. pipe(gulp.dest('build'));
});
CSS

CSS是完成前端构建过程的主要主题之一。 所有 CSS preprocessors都已经存在了很多时间,并且可以能比编译JavaScript的语言更有用或者使用。

在众多插件中,下面的插件似乎值得提及:

  • gulp-sass ( 将SASS转换为 CSS )
  • gulp-less ( 将 LESS 转换为 CSS )
  • gulp-minify-css ( 我们已经看到了)
  • gulp-autoprefixer ( 自动插入供应商特定的前缀)
  • gulp-imagemin ( 压缩图像)

Autoprefixer是非常有用的,但也强大地依赖于我们的需求( 大多数时候只调用 prefixer() 就足够了,但是有些人甚至需要支持最老的浏览器,因此需要更多的选项。)。 因此,我们将看到图像极小,它可以是一个 true 空间保护程序。 如上所述,前端性能优化从来都不是错误的,应该始终应用。

var gulp = require('gulp'),
 imagemin = require('gulp-imagemin');
gulp.task('images', function() {
 return gulp.src('src/images/**/*')
. pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
. pipe(gulp.dest('bin/images'));
});
Javascript

CSS非常重要,但更重要的是 JavaScript。 在这里我们很小心。 我们不仅希望在部署之前检测 Bug,还希望转换代码,甚至可以提取文档。 然而,Bug 检测是最核心的特性之一。 我们不仅希望运行潜在的单元测试,而且还希望执行一些 linting ( 通常命名为 static 代码分析)。 作为一个术语,在一般情况下也可以更广泛地引用语法差异,尤它的是在 JavaScript。

  • gulp-jshint ( 用于JavaScript的linting )
  • gulp-uglify ( 我们已经看到了)
  • gulp-jscs ( 检查是否违反了提供的样式指南)
  • gulp-jsdoc ( 提取文档)

下面的示例对src文件夹( 和子文件夹) 中的所有JavaScript文件执行一些 static 代码分析。 必须调用linter两次。 一旦与通常的创建者函数一起执行分析,另一次与记者进行分析,那么可以评估分析。 默认报告器将输出结果。 如果发生错误,这将不会导致任何失败,这就是为什么另一个报告者会失败的原因- so所谓的失败报告器。 我们可以把那个和 jshint.reporter('fail') 一起使用。

示例仅使用默认报告器,但输出附加的( 错误代码) 信息。

var gulp = require('gulp'),
 jshint = require('gulp-jshint');
gulp.task('lint', function() {
 return gulp.src('./src/**/*.js')
. pipe(jshint())
. pipe(jshint.reporter('default', { verbose: true }));
});
缓存和更多

直到现在一切都按照要求构建。 对于使用,系统的人来说,这可以能是一个巨大的步骤,它总是对最后的( 目标) 创建和最后的( 源文件) 修改。 但一般来说,这一过程既不可能,也不适合网络开发。 当然,我们不需要编译整个CSS内容,如果只修改了一个 JavaScript。 但这就是我们有不同任务的原因。 另一方面,如果我们合并并缩小JavaScript文件,就需要调用整个过程,即使只有一个文件更改。

不过,有时我们可以减少磁盘访问并插入一些缓存。 以下软件包可以用于进行某些筛选和优化生成过程:

  • gulp-cached ( 用于排除未更改文件和缓存结果的筛选器)
  • gulp-remember ( 与以前的,但省略的,结果相结合的过滤器)
  • gulp-changed ( 仅减少到只修改过的文件)

下面的示例使用 gulp-remembergulp-cached 来减少缩减工作的数量。 首先,我们只使用改变的子集为 uglified,然后添加先前跳过的内容。 这个想法是不能缓存或者记住结果( 合并的文件),但是单个文件( 源和缩小) 可以。 缓存因此做了两个事情,构建一个缓存并针对缓存使用( 例如。 过滤掉)。记住做两件事,记住流结果并与以前的结果相结合。

var gulp = require('gulp'),
 cached = require('gulp-cached'),
 uglify = require('gulp-uglify'),
 remember = require('gulp-remember'),
 concat = require('gulp-concat');
gulp.task('script', function(){
 return gulp.src('src/js/*.js')
. pipe(cached())
. pipe(uglify())
. pipe(remember())
. pipe(concat('app.js'))
. pipe(gulp.dest('bin/js'));
});

如果我们留出了合并文件的子任务,我们可以通过 gulp-changed 插件实现这一点。 但是,它更激进,因此不能在前面的示例中使用。

var gulp = require('gulp'),
 changed = require('gulp-changed'),
 uglify = require('gulp-uglify');
gulp.task('script', function(){
 return gulp.src('src/js/*.js')
. pipe(changed('bin/js'))
. pipe(uglify())
. pipe(gulp.dest('bin/js'));
});

因此,缓存的整体概念并不简单,非常微妙。 大多数时候最好稍等一会儿,但是有一个免费的构建过程,不排除所需的文件。

LiveReload和 BrowserSync

有多个插件可以自动关注用于查看输出的webbrowsers。 在手表的( 在 GREATER 深度下引入) 中,它们形成了一个伟大的组合。 这个想法是启动一个web服务器,它通过 web socket连接连接到 web browser。 使用特殊命令调用web服务器后,它会向所有连接的webbrowsers发送一条消息来刷新。 有更高级的场景和使用模型,但对于介绍来说,知识是足够的。

在下面的示例中,选择了 browser-sync 插件。 它不包含gulp-前缀,因此与gulp无关。 选择这个特定节点 MODULE的原因很简单: 它不需要任何浏览器插件。 提供服务器实现已经足够了。

var gulp = require('gulp'),
 minify = require('gulp-uglify'),
 sync = require('browser-sync');
gulp.task('js', function() {
 return gulp.src('src/*.js')
. pipe(minify())
. pipe(gulp.dest('bin'));
});
gulp.task('html', function() {
 return gulp.src('src/*.html')
. pipe(gulp.dest('bin'));
});
gulp.task('sync', function() {
 sync({ server: { baseDir: 'bin' } });
});
gulp.task('default', ['html', 'js', 'sync'], function() {
 gulp.watch(files.js, ['js', sync.reload]);
 gulp.watch(files.html, ['html', sync.reload]);
});

在下一节中,我们将很快介绍 livereload 插件,它类似,但更重量级。

手表

文件系统监视启用同步/实时构建过程非常方便。 我们设置了一个文件系统观察程序,一旦源文件发生更改,它就会被触发。 处理程序然后调用生成过程,它不仅提供了立即生成成功/失败报告,而且是一个最新的输出目录。

这种系统似乎是高需求的,这就是为什么吞吐创造者集成监视out-of-the-box的原因。 in Gulp监视glob对象( 就像一个遵循描述模式的字符串) 和相关任务。 一旦 MATCHES 文件改变,依赖任务将被执行。

下面是一个简单的示例:

gulp.task('watch-js', function() {
 gulp.watch('./src/js/*.js', ['js']);
});

这里我们创建了一个任务( 它被赋予了 NAME watch-js ),它只将一个文件观察者绑定到 src/js文件夹中的所有 *.js 文件。 一旦任何文件更改,js 任务将被触发。 但我们并不局限于这种用法。 更有趣的是浏览器同步的已经描述案例。 现在,我们可以触发浏览器重新加载。 当然这不是简单的任务,而是回调,但是吞咽提供了一个丰富的事件系统,可以通过 on 方法访问。 事件被称为 change。 我们只是绑定了 livereload 服务器实例的changed 方法。

gulp.task('watch', function() {
 // Rebuild Tasks livereload.listen();
 gulp.watch(['src/**'])
. on('change', livereload.changed);
});

将文件系统表和任务绑定在一起是很容易的( 而且非常方便)。 这实际上是Gulp的唯一卖点之一。 但是编写我们自己的插件也很简单。

编写插件

编写我们自己的插件也可能。 创建这样的插件实际上非常容易,特别是在遵循给定的样板代码时。 我们还应该遵循 Pattern 来返回一个创建函数,该函数创建一个新的through2 对象,并启用 objectModethrough2 是节点streams2变换周围的一个小包装,以避免显式子类化噪声。

在我们创建自己的插件之前,我们应该仔细阅读插件指南。 我们可以在 github.com/gulpjs/gulp/blob/master/docs/writing-a-plugin/guidelines.md 找到该指南。 当然我们应该先看看是否存在类似的插件,这可能是一个很好的基础,寻找贡献。 如果不是这样,我们需要检查我们所需要的插件是否足够。 否则,我们可能会在更多的插件中。

最后该指南还将告诉我们应该做什么。 首先我们应该独立于吞咽。 那是什么是的A 插件独立于吞咽本身。 ! 如果我们考虑到这一点。 for提供了仅用于定义任务和运行它们的方法。 但是一个插件在一个任务中被使用,因此与通常的工作分配无关。 但是,应该包含的一个依赖项是 gulp-util。 包含从通知连接到错误处理的所有内容。 如果我们抛出异常,我们应该通过 gulp-util 模块提供的PluginError 类来实现。

一旦准备发布插件,我们也应该使用 gulpplugin 标签。 在 package.json 中添加 gulpplugin 作为关键字是有益的,这样插件也会显示在官方搜索中。

那么样板代码看起来像?

var through = require('through2'),
 gutil = require('gulp-util'),
 PluginError = gutil.PluginError;
module.exports = function() {
 return through.obj(function(file, enc, cb) {
 /*.. . */ }));
};

那么,回调中的选项是什么? 首先我们得到了虚拟文件,它是最重要的参数。 我们应该用 file.isNull() 测试空的集合,在这里我们直接返回。 否则,我们需要区分缓冲区,file.isBuffer() 和流: file.isStream()。我们还可以测试给定记录是否是通过 file.isDirectory()的目录。

第二个选项是提供的文件编码。 这只对文本文件很有趣,但在某些情况下可能有用。 finally 提供回调 cb,用于返回( 可能已经修改) 流 比如 cb(null, file);

使用代码

本文附带的源是一个GitHub存储库,其中包含大量的Gulp示例。 你可以通过以下 github.com/FlorianRappl/GulpSamples 找到存储库。 代码是纯的,不包括依赖项的换句话说,。 你将需要在特定示例的目录中运行 npm install。 在运行任何 gulp 命令之前,你还应该安装 Gulp。 其他需求如节点和npm很明显。

有一个可以能的例子,一个或者另一个例子不起作用,当你尝试运行它时。 如果直接从GitHub获取样本的当前版本,那么机会肯定是较低的。 但即便如此,Gulp或者某些插件的破坏也可能发生。 在这种情况下,示例需要更新。

Gulp或者 Grunt?

现在我当然是使用Gulp的有力支持者。 不过,这只是个人观点。 如果你使用的是 Grunt ( 或者任何其他构建系统) 和感觉,那就是对你来说是正确的,然后坚持。 构建系统的作用是什么? 它应该完成它的工作。 不应该在你的路上。 你不需要调试生成系统。

Gulp在许多订单上都很好,但它并不完美。 然而,Grunt也不是一个免费的免费参赛者。 两个系统都有各自的优点和缺点。 如果你在我的位置上,我唯一的建议就是同时尝试。 如果你完全在编程,那么吞咽可能会感觉对。 如果你更喜欢设计或者管理,那么Grunt可能是你的理想选择。

如果你询问 Preslav Rachev,如果吞咽Grunt过时,你可能会听到如下内容: "没有,因为Burger没有make麦当劳的obsolete。 事实是,如果你开始使用js基于基于构建系统的系统,那么你可能会很快选择。 它的前景似乎有点更加加强,而且它已经被大部分项目所采用。 然而,如果你仍然使用 Grunt,感觉舒适,没有什么值得担心的- 社区仍然存在,大于吞咽,并不断增长。 to几乎有几年的优势,我相信大型项目维护人员会尽可以能坚持它。 如果你需要的一切就是简单的任务集合,使用voiceover的另一个优点就是。 在这种情况下,你会感觉到它在家里,尽管它的灵活性,会使你花更多的时间。 其他项目相关的项目一样,你应该根据情况选择。"

像往常一样,为工作选择合适的工具,包括你的个人经验和偏好。

兴趣点

这篇文章是一个详细的版本,在上年在WebTechConference的上给出了一个话题。 我认为谈话确实留下了印象,大多数听众至少尝试了 Gulp。 我收到一些关于Gulp用法的非常积极的反馈。 我个人认为,吞咽适合紧急需要。 如果你对Grunt或者其他构建系统满意,那么就坚持使用它。 但是如果你觉得不适合你使用的那个,或者想尝试新的东西,那么当你想要的时候。

Gulp快速,非常优雅,易于扩展。 我提到过它也很容易? 一个小时的谈话通常会涵盖从基础到编写我们自己的插件的一切。 这 很 了不起。 在一个快速运行的世界中,技术很有趣且易于学习,但足以覆盖所有的需求。 Gulp通过回到CS基础,成功地完成了这项工作: 提供可以粘合在一起的小型构造块。

不要忘了查看GitHub存储库。 如果我有其他示例,我会不时地添加更多的样本,并且很乐意接受其他示例的请求请求,如果你觉得有什么问题。 如果一个或者它的他样例不能再工作,也可以留下一个消息,而且我没有限制( 或者任何插件)的版本。 如果示例失败,应更新。 这些样例应该与最新的资料一起使用。

历史

  • v1.0.0 | 初始发行版|
  • |,some typos typos
  • v1.1.0 | 包括 Gulp vs Grunt讨论| 02.02.2015
  • v1.1.1 | 固定 ASP.NET 版本编号|

INT  introduction  GULP  
相关文章