原文连接:The Complete-Ish Guide to Upgrading to Gulp 4javascript
虽然Gulp4始终在开发中,可是你要坚信在未来的某一天你必定能够等到它的正式版。嗯,某一天。因此如今我想先向大家介绍Gulp3.x和Gulp4之间的不一样,同时但愿可以帮助你未来能相对无痛的迁移到新的版本。java
在你开始使用最新版的Gulp以前,你必需要先检查一下你Gulp的版本。一般,你只须要更新你的package.json
中的版本号就好了,不过有时候你也有可能碰到一些额外的麻烦。最可能的缘由是你分别在项目文件夹下和全局环境中都安装了Gulp(若是你读过了这篇文章the practice of using npm scripts to access the locally installed version of CLI’s,那就好办多了。虽然在这里它可能仍是帮不了你太多)。所以,首先你要把你项目文件夹下的Gulp删除,若是你在全局环境中也安装了Gulp,最好也把它删了。git
npm uninstall gulp --save-dev npm uninstall gulp -g
如今你就能够在你的项目中安装Gulp4。因为它尚未正式发布,咱们只能直接经过Github来安装它:github
npm install gulpjs/gulp.git#4.0 --save-dev
当它提交到npm库以后,你就能够像日常同样使用npm install gulp --save-dev
了。而且当它发布正式版本后,咱们也最好不要从Github上安装,改成直接从npm上进行安装。好了,如今咱们还有另外一个东西须要安装:命令行工具。跟如今的Grunt相似的,Gulp4把命令行工具从Gulp的核心代码中剥离了。Gulp3和Gulp4都能使用独立出来的命令行工具。npm
npm install gulp-cli --save-dev
若是你不想在你的项目中使用npm scripts,你须要使用-g
替换-save-dev
来进行全局安装。如今你就能够像之前同样使用gulp
命令了,可是你应该会获得一个错误信息,由于你须要更新你的gulpfile.js
来兼容最新版的Gulp。json
若是你原来的任务代码结构十分简单,任务以前没有相互的依赖。那很走运,你将不须要作任何修改!不过使人哀伤的是,大部分人都不得不作一些调整。Gulp4最大的一个改变就是gulp.task
函数如今只支持两个参数,分别是任务名和运行任务的函数。举个例子,下面的任务代码能够很好的运行在Gulp3和Gulp4上面:gulp
gulp.task('clean', function() {...})
可是当你使用三个参数时该怎么办?咱们要如何指定任务之间的依赖关系?这时新的gulp.series
和gulp.parallel
函数应该能帮助你解决难题。这两个函数均可以接受数个函数或任务名做为参数,通过组合后,返回一个新的函数。gulp.series
会返回一个函数用来顺序执行它所接受的任务/函数,而gulp.parallel
返回的函数则会并行的运行它们。Gulp总算可以让咱们自由的选择以串行或并行的方式来执行任务而再也不须要其余的第三方依赖(好比经常使用的run-sequence),也不用再定义一堆让人看不懂的任务依赖。数组
若是你之前是这么写:promise
gulp.task('styles', ['clean'], function() { ... });
那你如今能够这样:异步
gulp.task('styles', gulp.series('clean', function() { ... }));
在改写的时候,不要忘了其实如今你处理主要任务的函数也是放在gulp.series里面调用,因此不要忘了在结尾加上括号。不少人常常犯这个错误。
注意,因为gulp.series
和gulp.parallel
返回的是一个函数,因此他们是能够被嵌套调用的。若是您的任务每每有多个依赖任务,你会常常嵌套调用它们。好比这个例子:
gulp.task('default', ['scripts', 'styles'], function() { ... });
你能够改写成:
gulp.task('default', gulp.series(gulp.parallel('scripts', 'styles'), function() { ... }));
看过去,这样代码读起来很是吃力。不过考虑到这样会使你任务流程控制更加的灵活,这点牺牲也就无所谓了。固然我以为你也能够本身封装一些helper/alias函数来优化的你的代码,提升可读性,但我应该不会这么去作。
在Gulp3中,假设你设定几个有相同依赖的任务,而后运行它们,Gulp会检测出这些将要运行的任务的依赖是同样的,而后只会运行一次依赖任务。然而如今咱们再也不显式的指定任务之间的依赖,而是经过series和parallel函数来组合任务,这样会致使那些本应该只运行一次的任务,变成屡次运行。Gulp4是没法作出相应的区分的。因此咱们要改变咱们指定任务依赖的思路。
让咱们看一下这个Gulp3的例子:
// default任务,须要依赖scripts和styles gulp.task('default', ['scripts', 'styles'], function() {...}); // script折styles任务都依赖clean gulp.task('styles', ['clean'], function() {...}); gulp.task('scripts', ['clean'], function() {...}); // clean任务用来清空目录 gulp.task('clean', function() {...});
咱们注意到styles
和scripts
任务都依赖clean
任务。当你运行default
任务时,Gulp3会率先运行styles
和scripts
任务,又由于检测到这两个任务都有各自的依赖,因此须要优先运行它们的依赖任务,这时Gulp注意到这两个任务都依赖于clean
,因而Gulp3将确保在回到styles
和scripts
任务以前,clean
任务会被执行且执行一次。这颇有用!但遗憾的是,咱们在新版本中将没办法运用这个特性。若是你在迁移到Gulp4的过程当中只像下面的例子同样作了简单的改变,clean
任务将会被执行两次:
gulp.task('clean', function() {...}); gulp.task('styles', gulp.series('clean', function() {...})); gulp.task('scripts', gulp.series('clean', function() {...})); gulp.task('default', gulp.parallel('scripts', 'styles'));
这是由于parallel
和series
不是用来解决依赖的;他们只是用来把多个任务合并成一个。因此咱们须要把共同依赖的任务抽离出来,而后用一个更大的串行任务来包裹它们,以此来模拟任务依赖关系:
友情提示:你最好不要在定义那些小任务以前就用它们来组合你的default
任务。由于在你调用gulp.series("taskName")
以前,你必须已经定义好了一个名为"taskName"
的任务。因此通常在Gulp4中,咱们会在代码的最后才定义default
,而在Gulp3中你能够把它放在任何地方。
// 任务直接再也不有依赖 gulp.task('styles', function() {...}); gulp.task('scripts', function() {...}); gulp.task('clean', function() {...}); // default任务,须要依赖scripts和styles gulp.task('default', gulp.series('clean', gulp.parallel('scripts', 'styles')));
若是照这么写,当你单独运行styles
和scripts
任务时,clean
任务就不会优先自动执行。不过这问题也不大,在以前单独运行clean
任务就能够了,同样能把scripts和styles的文件夹清空。又或者你能够从新定义一下你的任务,随你,我也不肯定怎样会更好。
若是你执行的是同步任务,在Gulp3中不须要写任何其余代码,可是在Gulp4中就不能如此轻松了:如今也你必须运行done回调(这多是我最先发现的一个变化)。而后若是你执行的是异步任务,你则有三个选择来确保Gulp可以检测到你的任务真的完成了,方法以下:
你能够在你的任务函数的参数中提供一个回调函数而且在你的任务完成后调用它:
var del = require('del'); gulp.task('clean', function(done) { del(['.build/'], done); });
你也能够返回一个流,一般经过gulp.src
或vinyl-source-stream这个库来建立。这通常也是最经常使用的方式:
gulp.task('somename', function() { return gulp.src('client/**/*.js') .pipe(minify()) .pipe(gulp.dest('build')); });
Promise这个技术早已声名鹊起并且在Node中已经有了完整的实现,因此这也会是一个颇有用的方式。你只须要返回一个promise对象,Gulp就能知道任务在何时完成。
var promisedDel = require('promised-del'); gulp.task('clean', function() { return promisedDel(['.build/']); });
感谢Gulp如今引入了async-done库,在最新的版本中咱们有更多的方式来确认异步任务的完成。
你能够在你的任务中建立一些子进程并返回!好比,你能够把你的npm scripts放到Gulp中执行,这样你就不须要为你的package.json中加载了百万条命令而烦恼。你也能够经过这样的封装摆脱那些随时可能过期的gulp插件。尽管这看上去像一个反模式,不过你仍是有不少能够优化它们的方法。
var spawn = require('child_process').spawn; gulp.task('clean', function() { return spawn('rm', ['-rf', path.join(__dirname, 'build')]); });
我没用过RxJS,它好像挺小众的。不过对于那些RxJS的死忠粉丝,他们会很高兴能够返回一个observable对象。
var Observable = require('rx').Observable; gulp.task('sometask', function() { return Observable.return(42); });
处理文件系统的监听和响应的API也有了一点进步。以前的API中,在咱们传入一个glob通配符和可选参数后,咱们能够再指定一个任务数组或者一个回调函数用来处理事件数据。但是如今,任务队列都是由serise或者parallel函数合并而成,这样你就没法用一个回调来区分这些任务,因此咱们取消了这种简单监听回调的方式。取而代之的是,gulp.watch
将像以前同样会返回一个的“观察”对象,不过你能够对它添加各类事件监听:
// 旧版 gulp.watch('js/**/*.js', function(event) { console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); }); // 新版: var watcher = gulp.watch('js/**/*.js' /* 你能够在这里传一些参数或者函数 */); watcher.on('all', function(event, path, stats) { console.log('File ' + path + ' was ' + event + ', running tasks...'); }); // 单个事件的监听 watcher.on('change', function(path, stats) { console.log('File ' + path + ' was changed, running tasks...'); }); watcher.on('add', function(path) { console.log('File ' + path + ' was added, running tasks...'); }); watcher.on('unlink', function(path) { console.log('File ' + path + ' was removed, running tasks...'); });
正如所看到的,在all
和change
的事件处理中,你还能够接受一个stats对象。stats对象只在他们可用的时候出现(我也不肯定他们何时可用何时不可用),不过你能够设置alwaysStat
选项的值为true
来让它始终出现。Gulp使用了chokidar库来实现这些东西,阅读chokidar的文档能让你了解的更多,尽管chokidar并不支持在事件回调中指定第三个参数。
因为如今每一个任务基本上就是一个函数,不须要任何依赖或其余的什么,实际上他们也仅仅是须要一个任务运行器来确认异步任务什么时候结束,咱们能够把函数定义从gulp.task
中独立出来,而不只仅做为一个简单回调函数传给gulp.task
。举个例子,这个代码以前咱们在“依赖陷阱”那个章节的结论:
gulp.task('styles', function() {...}); gulp.task('scripts', function() {...}); gulp.task('clean', function() {...}); gulp.task('default', gulp.series('clean', gulp.parallel('scripts', 'styles')));
我把它变成:
// 只须要在`series` 和 `parallel` 中间引用函数名就能组成一个新任务 gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles))); // 把单个任务变成一个函数 function styles() {...} function scripts() {...} function clean() {...}
这里有几点要注意的地方:
1.因为js是有函数定义提高的,函数的定义能够放在你定义default任务以后,不像以前说的,若是你要用一些小任务组成一个新任务的时候,你就必需要先定义那些小任务。这样就使得你能够在一开始就定义好实际要运行的任务,这样别人阅读起来也更方便一些,以避免别人还要在翻阅了一堆其余任务代码后,才能发现藏在最后的实际要运行的那些。
2.styles
, scripts
, 和 clean
如今都至关于“私有”任务,他们没法经过gulp命令行来运行。
3.这样就没有那么多匿名函数了。
4.也没有那么多被引号包裹住的“任务”名了,这样意味着你能够经过你的代码编辑器/IDE帮你检查拼写错误,而不用在运行Gulp的时候才能发现错误。
5.即便把“任务”函数放在多个文件中定义,也能方便的把它们引用到同一个文件中,而后再经过gulp.task
把它们变成实际可用的任务。
6.这些任务都是能够独立测试的(若是你要测试)而不须要gulp。
固然第2点也是能够修改的,若是你但愿它们是能够被gulp命令行所执行的:
gulp.task(styles);
这样你就能新建了一个能够运行在命令行的“styles”任务。注意你可历来没有在代码中定义过它的名字。gulp.task能够很智能的把函数名转成任务名。固然,匿名函数是不行的:Gulp会抛出一个错误当你想要把匿名函数指定成一个任务,却没有给它起一个新名字。
若是你想给函数起个别名,你能够在函数的displayName
属性中指定它:
function styles(){...} styles.displayName = "pseudoStyles"; gulp.task(styles);
如今任务名将会从“styles”变成“pseudoStyles”。你也能够经过指定description属性来给你的任务添加描述。你能够经过gulp --tasks命令来查看这些描述:
function styles(){...} styles.displayName = "pseudoStyles"; styles.description = "Does something with the stylesheets." gulp.task(styles);
$ gulp --tasks [12:00:00] Tasks for ~/project/gulpfile.js [12:00:00] └── pseudoStyles Does something with the stylesheets.
你甚至能够给你其余已经注册的任务添加描述,好比default
。首先你要运行gulp.task('taskName')
来取人这个任务已经被定义过了,而后才给它添加描述:
gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles))); // Use gulp.task to retrieve the task var defaultTask = gulp.task('default'); // give it a description defaultTask.description = "Does Default Stuff";
咱们也能够简化它,取消中间值:
gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles))); gulp.task('default').description = "Does Default Stuff";
对那些不熟悉你的项目的人来讲,这些描述是至关有用的。因此我建议在任何状况下都要添加它:有时它比注释还更有用。最后总结一下,这是我推荐的Gulp4的最佳实践:
gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles))); gulp.task('default').description = "This is the default task and it does certain things"; function styles() {...} function scripts() {...} function clean() {...}
若是你运行gulp --tasks
,你将会看到:
$ gulp --tasks [12:00:00] Tasks for ~\localhost\gulp4test\gulpfile.js [12:00:00] └─┬ default This is the default task and it does certain things [12:00:00] └─┬ <series> [12:00:00] ├── clean [12:00:00] └─┬ <parallel> [12:00:00] ├── scripts [12:00:00] └── styles
你会发现这里不只有你添加的描述,你还能看到完整的运行队列树。我也很乐意听到你对最佳实践有其余见解,不过在阐述结论前最好先跟你的团队讨论一下。
无论怎么样,我仍是很高兴看到Gulp4有不少有用的改进,可是它们也给迁移带来了很多痛苦。我但愿这份指南能帮助你顺利迁移到Gulp4当它正式发布后(可能过几天……也可能……)。上帝保佑~