Webpack与Gulp、Grunt没有什么可比性,它能够看做模块打包机,经过分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。Gulp/Grunt是一种可以优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优势使得Webpack在不少场景下能够替代Gulp/Grunt类的工具。html
他们的工做方式也有较大区别:前端
Grunt和Gulp的工做方式是:在一个配置文件中,指明对某些文件进行相似编译,组合,压缩等任务的具体步骤,工具以后能够自动替你完成这些任务。java
Webpack的工做方式是:把你的项目当作一个总体,经过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的全部依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。node
grunt vs gulp
虽然gulp已经出来好久了,可是一直没有去使用过。得益于最近项目须要,就尝试了一下,如下从几个要点讲一下grunt和gulp使用的区别,侧重讲一下在使用gulp过程当中发现的问题。而两种工具孰优孰劣由读者本身判断。jquery
1. 书写方式
grunt 运用配置的思想来写打包脚本,一切皆配置,因此会出现比较多的配置项,诸如option,src,dest等等。并且不一样的插件可能会有本身扩展字段,致使认知成本的提升,运用的时候要搞懂各类插件的配置规则。
gulp 是用代码方式来写打包脚本,而且代码采用流式的写法,只抽象出了gulp.src, gulp.pipe, gulp.dest, gulp.watch 接口,运用至关简单。经尝试,使用gulp的代码量能比grunt少一半左右。git
2. 任务划分
grunt 中每一个任务对应一个最外层配置的key, 大任务能够包含小任务,以一种树形结构存在。举个栗子:github
uglify: {
one: { src: 'src/a.js', dest: 'dest/a.min.js' }, two: { src: 'tmp/b.js', dest: 'dist/b.min.js' } }
将uglify划分子任务的好处是,咱们在封装不一样的task时能够分别对'uglify:one'或'uglify:two'进行调用,这对于某些须要在不一样时间点调用到uglify的task至关有用。npm
gulp 中没有子任务的概念,对于上面的需求,只能经过注册两个task来完成
gulp.task('uglify:one', function(){ gulp.src('src/a.js') .pipe(uglify()) .dest('dest/a.min.js') }); gulp.task('uglify:two', function(){ gulp.src('tmp/b.js') .pipe(uglify()) .dest('dist/b.min.js') });
固然这种需求每每能够经过调整打包策略来优化,并不须要分解子task,特殊状况下能够用这种方法解决。
3. 运行效率
grunt 采用串行的方式执行任务,好比咱们注册了这样一个任务:grunt.register('default', ['concat', 'uglify', 'release'])
grunt是按书写的顺序首先执行cancat,而后是uglify,最后才是release,一派和谐的气氛,谁也不招惹谁。而咱们知道某些操做时能够同步执行的,好比cssmin和uglifyjs。这时grunt没法经过简单地更改配置来达到并行执行的效果,一般的作法是手动写异步task,举个栗子:
grunt.registerTask('cssmin', 'async cssmin task', function() { var done = this.async(); cssmin(done); });
在cssmin操做完成后传入done方法告知程序,但这须要插件支持。
gulp 基于并行执行任务的思想,经过一个pipe方法,以数据流的方式处理打包任务,咱们来看这段代码:
gulp.task('jsmin', function () { gulp.src(['build/js/**/*.js']) .pipe(concat('app.min.js')) .pipe(uglify() .pipe(gulp.dest('dist/js/')); });
程序首先将build/js
下的js文件压缩为app.min.js
, 再进行uglify操做,最后放置于dist/js下。这一系列工做就在一个task中完成,中间没有产生任何临时文件。若是用grunt,咱们须要怎样写这个任务?那必须是有两个task配置,一个concat,一个uglify,中间还必须产生一个临时文件。从这个角度来讲,gulp快在中间文件的产生只生成于内存,不会产生多余的io操做。
再来看看前面的问题,如何并行执行uglify和cssmin?其实gulp自己就是并发执行的,咱们并不须要多什么多余多工做,只需
gulp.task('default', ['uglify', 'cssmin']);
gulp该怎么快就怎么来,并不会等到uglify再执行cssmin。
是否是以为gulp秒杀grunt几条街了呢?且慢,坑还在后面...
首先咱们须要问一个问题,为何要用并发?
为了快?那何时能够快,何时又不能快?
假设咱们有这样一个任务:
gulp.task('jsmin', ['clean', 'concat']);
须要先将文件夹清空,再进行合并压缩,根据gulp的并发执行的方式,两个任务会同时执行,虽然从指令上看是先执行了clean再执行concat,然而clean还没结束,concat就执行了,致使代码合并了一些未被清理的文件,这显然不是咱们想要的结果。
那这个问题有没有什么解决方案呢?
gulp官方API给出了这样的方法:
- 给出一个提示,来告知 task 何时执行完毕
- 而且再给出一个提示,来告知一个 task 依赖另外一个 task 的完成
官方举了这个例子:
让咱们先假定你有两个 task,"one" 和 "two",而且你但愿它们按照这个顺序执行:
1. 在 "one" 中,你加入一个提示,来告知何时它会完成:能够再完成时候返回一个 callback,或者返回一个 promise 或 stream,这样系统会去等待它完成。
2. 在 "two" 中,你须要添加一个提示来告诉系统它须要依赖第一个 task 完成。
所以,这个例子的实际代码将会是这样:
var gulp = require('gulp'); // 返回一个 callback,所以系统能够知道它何时完成 gulp.task('one', function(cb) { // 作一些事 -- 异步的或者其余的 cb(err); // 若是 err 不是 null 或 undefined,则会中止执行,且注意,这样表明执行失败了 }); // 定义一个所依赖的 task 必须在这个 task 执行以前完成 gulp.task('two', ['one'], function() { // 'one' 完成后 }); gulp.task('default', ['one', 'two']);
task one执行完毕后须要调用cb方法来告知task two我已经执行完成了,你能够干你的事了。
那在咱们实际运用中,一般是这样的:
gulp.task('clean', function (cb) { gulp.src(['tmp']) .pipe(clean()); });
这个时候clean结束的cb要写在哪呢?是这样吗?
gulp.task('clean', function (cb) { gulp.src(['tmp']) .pipe(clean()); cb(); });
对于理解什么叫异步的人来讲这种方法确定是不行的,clean还没完成,cb已经执行了。好在!!!
好在咱们能够利用gulp中的时间监听来作结束判断:
gulp.task('clean', function (cb) { gulp.src(['tmp']) .pipe(clean()), .on('end', cb); }); gulp.task('concat', [clean], function(){ gulp.src('blabla') .pipe('blabla') .dest('blabla'); });
因为gulp是用node实现的,因此必然绑定了数据流的监听事件,咱们经过监听stream event end来达到这个目的。
而不得不吐槽的是经过在task后面写[]依赖的方式也并不优雅,一般能够经过其余插件来达到顺序执行的效果,写法如同grunt,可是每一个task的end事件的监听也是少不了的。
若是你的任务很少的时候,直接在回调后面执行concat也是能够的:
gulp.task('clean', function(){}) gulp.task('concat', function(){}) gulp.task('clean-concat', ['clean'], function(){ gulp.start('concat'); })
4. 其余要交代的
- gulp真的只有src, pipe, dest, watch, run这几个API吗? 不,因为gulp继承了Orchestrator(<4.0),因此具有了另一些API,包括start等。固然这些API是官方不推荐使用的。会致使代码的复杂度提高,因此并无出如今官方文档中。
- 不建议将多个操做写在同个task中,这样程序并不知道任务及时结束,如:
gulp.task('test', function(cb) { gulp.src('bootstrap/js/*.js') .pipe(gulp.dest('public/bootstrap')) .on('end', cb); gulp.src('jquery.cookie/jquery.cookie.js') .pipe(gulp.dest('public/jquery')) .on('end', cb); });
- 尽可能减小task的数量,不少任务其实能够在一个task中用多个pipe来执行,只须要咱们在打包等时候规划好文件夹及任务流。
对了,gulp4.0会带给咱们不少惊喜(wtf!),虽然它仍是迟迟未发布... 暂时不想去踩坑。读者可自行Google。