Grunt 一直是前端领域构建工具(任务运行器或许更准确一些,由于前端构建只是此类工具的一部分用途)的王者,然而它也不是毫完好陷的,近期风头正劲的 gulp.js 隐隐有取而代之的态势。那么,到底是什么使得 gulp.js 备受关注呢?javascript
gulp.js 的做者 Eric Schoffstall 在他介绍 gulp.js 的 presentation 中总结了 Grunt 的几点不足之处:html
插件很难遵照单一责任原则。由于 Grunt 的 API 设计缺憾,使得许多插件不得不负责一些和其主要任务无关的事情。好比说要对处理后的文件进行改名操做,你可能使用的是 uglify
插件,也有可能使用的是 concat
插件(取决于工做流的最后一个环节是谁)。前端
个人见解:这或许是个问题,对不少人来讲 Grunt 插件多少存在“职责不明”和“越俎代庖”的状况。在我看来,这也是 Grunt 一个设计思想:把对文件的操做抽象为一个独立的组件(Files),任何插件都以相同的规则来使用它。遗憾在于,使用它的过程发生在每一个插件的独立配置对象里,因此总给人一种“把不应这个插件作的事情丢给它来作”的别扭感受。html5
用插件作一些原本不须要插件来作的事情。由于 Grunt 提供了统一的 CLI 入口,子任务由插件定义,由 CLI 命令来调用执行,所以哪怕是很简单的外部命令(好比说运行 karma start
)都得有一个插件来负责封装它,而后再变成 Grunt CLI 命令的参数来运行,画蛇添足。java
个人见解:举双手双脚同意!node
试图用配置文件完成全部事,结果就是混乱不堪。规模较大,构建/分发/部署流程较为复杂的项目,其 Gruntfile
有多庞杂相信有经历的人都有所体会。而 gulp.js 奉行的是“写程序而不是写配置”,它走的是一种 node way。git
个人见解:对于 node.js 开发者来讲这是好事,符合他们的一向做风;不过对于那些纯前端工程师来讲(数量不小),这彷佛没有什么显著的改善。何况近来 Grunt 社区涌现了很多插件来帮助开发者组织/管理/简化臃肿的
Gruntfile
,效果都还不错。因此关于这一点,就见仁见智吧。github
落后的流程控制产生了让人头痛的临时文件/文件夹所致使的性能滞后。这是 gulp.js 下刀子的重点,也是本标题里“流式构建”所解决的根本问题。流式构建改变了底层的流程控制,大大提升了构建工做的效率和性能,给用户的直观感受就是:更快。shell
个人见解:关于流式构建,短短几句话没法讲清它的前因后果,可是在 node.js 的世界里,
streaming
确实是相当重要的。我推荐一份阅读材料:Stream Handbook,读过以后相信内心就有数了。npm
做为对比和总结,做者列出了 gulp.js 的五大特色:
gulp.js 的官方文档都在 Github 上,本文是一个简介,更具体的细节还请自行阅读文档。在这里我就 gulp.js 的安装和使用流程作一个简述,先一块儿来领略一下 gulp.js 的风采吧。
$ npm install -g gulp
package.json
)$ cd <YOUR_PROJECT> $ npm install gulp --save-dev
Gulpfile.js
,初始内容为:var gulp = require('gulp'); gulp.task('default', function () { });
$ gulp
So far so good! 看起来和 Grunt 没差太远吧?的确如此,gulp.js 的学习曲线仍是至关平缓的。接下来,为了可以顺利的编写构建脚本,咱们来学习几个核心的 API 函数——别担忧,gulp.js 的 API 很是简单,咱们只须要了解四个就足以应对绝大多数的脚本编写了(并且用过 Grunt 的话,这四个都不是什么新鲜货)。
gulp.task(name[, deps], fn)
:注册任务name
是任务名称;deps
是可选的数组,其中列出须要在本任务运行要执行的任务;fn
是任务体,这是 gulp.js 的核心了,须要花时间吃透它,详情见此。
gulp.src(globs[, options])
:指明源文件路径
用过 Grunt 的话,globs
必定不会陌生,这里没什么变化;options
是可选的,具体请查看 gulp.js API
gulp.dest(path)
:指明任务处理后的目标输出路径
gulp.watch(glob[, options], tasks)/gulp.watch(glob[, options, cb])
:监视文件的变化并运行相应的任务。你没看错,watch
做为核心 API 出如今 gulp.js 里了,具体用法仍是要多看文档,不过接下来咱们会演示简单的例子。
咱们练习一个最多见的范例,写一个 node.js 程序时所须要的构建脚本。为此咱们要作三件事情(括号内列出对应插件的名字,更多插件请到此处寻找):
gulp-jshint
)gulp-concat
)gulp-uglify
)另外,咱们可能还须要文件改名操做,因此 gulp-rename
也会颇有用。接着咱们须要先在项目下安装这些插件:
$ npm install <PLUGIN_NAME> --save-dev
最后咱们完成全部任务的编写,完整的代码以下:
var gulp = require('gulp'); var jshint = require('gulp-jshint'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); var rename = require('gulp-rename'); // 语法检查 gulp.task('jshint', function () { return gulp.src('src/*.js') .pipe(jshint()) .pipe(jshint.reporter('default')); }); // 合并文件以后压缩代码 gulp.task('minify', function (){ return gulp.src('src/*.js') .pipe(concat('all.js')) .pipe(gulp.dest('dist')) .pipe(uglify()) .pipe(rename('all.min.js')) .pipe(gulp.dest('dist')); }); // 监视文件的变化 gulp.task('watch', function () { gulp.watch('src/*.js', ['jshint', 'minify']); }); // 注册缺省任务 gulp.task('default', ['jshint', 'minify', 'watch']);
能够看出,基本上全部的任务体都是这么个模式:
gulp.task('任务名称', function () { return gulp.src('文件') .pipe(...) .pipe(...) // 直到任务的最后一步 .pipe(...); });
很是容易理解!获取要处理的文件,传递给下一个环节处理,而后把返回的结果继续传递给下一个环节……直到全部环节完成。pipe
就是 stream
模块里负责传递流数据的方法而已,至于最开始的 return
则是把整个任务的 stream
对象返回出去,以便任务和任务能够依次传递执行。
或许写成这样会更直观:
gulp.task('task_name', function () { var stream = gulp.src('...') .pipe(...) .pipe(...) // 直到任务的最后一步 .pipe(...); return stream; });
至此,你已经可使用 gulp.js 完成绝大多数的构建工做了。下一步,我也为你准备了几条建议:
gulp.task()
里关于任务体的详细描述,学会如何执行回调函数(callback),如何返回 promise
等等