Gulp 3 升级到Gulp 4(安装)
使用最新版Gulp之前,检查一下Gulp的版本。我们通常只需更新package.json中的版本号,但可能会有遇到问题。原因多半是因为分别在项目文件夹下和全局环境中都安装了Gulp。如果是这样我们最好把项目文件夹和全局环境中的Gulp都删了。
npm uninstall gulp --save-dev
npm uninstall gulp -g
然后就是安装Gulp 4
npm install gulp@4 --save-dev
还有另一个东西需要安装:命令行工具。跟Grunt类似的,Gulp 4 把命令行工具从Gulp的核心代码中剥离了。Gulp 3 和Gulp 4 都能使用独立出来的命令行工具。
npm install gulp-cli --save-dev
如果不想在项目中使用npm scripts,需要使用-g替换-save-dev来进行全局安装。这样就可以像以前一样使用gulp命令,但是应该会得到一个错误信息,因为Gulp 3 和Gulp 4 的 gulpfile.js 配置方法不一样。
重构任务(Tasks)
如果只需一些简单的没有依赖的任务,不需要做任何改变。但实际上是,大部分人都需要做出改变。最大的改变就是 Gulp 现在只支持 2 个参数的 gulp.task。当使用两个参数时,它接受字符串形式的任务名,以及该任务对应的函数。例如,在版本 3.x 与版本 4 中,下面的任务代码保持不变:
gulp.task('clean', function() {...})
但 3 个参数的形式该怎么办呢?我们如何指定一个具依赖的任务?你可以使用新的 gulp.series 和 gulp.parallel 来解决这个问题。这两个函数都接受一个函数或者任务名列表,然后返回新的函数。gulp.series 返回一个按给定的任务 / 函数的顺序执行的函数,而 gulp.parallel 则返回一个能并行执行给定的任务/函数的函数。终于,Gulp 提供了选择顺序或并行执行任务的能力,而不用添加别的依赖(传统上是使用 run-sequence)或者丧心病狂地人工分配任务的依赖。
所以,如果之前有这样一个任务:
gulp.task('styles', ['clean'], function() {
...
});
它将会变为:
gulp.task('styles', gulp.series('clean', function() {
...
}));
当做出这个改变时,不要忘了你的任务函数现在在 gulp.series 的回调函数里。所以你需要在尾部多出来的那个括号。这很容易被忽略。
注意到 gulp.sereis 与 gulp.parallel 会返回函数,所以它们可以被嵌套。当你的任务有多个依赖时,可能需要经常地嵌套它们。
例如,这个常见的模式:
gulp.task('default', ['scripts', 'styles'], function() {
...
});
将会变为:
gulp.task('default', gulp.series(gulp.parallel('scripts', 'styles'), function() {
...
}));
不幸的是,这读起来通常会比原来的方法混乱了一些。但这只是为获得更大的灵活性和更简单的操控所付出的小小代价。如果你愿意的话,你也可以写一些辅助函数来使其更简练,但我不会那样做。
依赖陷阱
在 Gulp 3 中,如果你为多个任务指定了同一个依赖,并且它们都在运行时,Gulp 会意识到它们都依赖相同的任务,然后只执行一次这个被依赖的任务。因为在 Gulp 4 中,我们不再指定"依赖",而是使用 series 或 parallel 来组合函数,这导致 Gulp 不能判断哪些任务在当它只应运行一次时会被多次运行。所以我们需要改变我们对依赖的处理方式。
由于到处都有很多术语,所以我们用一个例子来区分他们。这个例子由改编于一篇基于前端技术讲座上关于 Gulp 4 新的任务执行系统的讨论的文章。他们在这个话题上讨论了很久,所以如果我讲的不够清楚,这篇文章应该能让你更清晰地明白。
看一看 Gulp 3 的一个例子:
// default 任务依赖于 scripts 与 styles
gulp.task('default', ['scripts', 'styles'], function() {...});
// styles 与 scripts 都依赖于 clean
gulp.task('styles', ['clean'], function() {...});
gulp.task('scripts', ['clean'], function() {...});
// clean 将删除构建目录
gulp.task('clean', function() {...});
Gulp 注意到 styles 与 scripts 任务都依赖于 clean 任务,所以当执行默认任务时,它将试图同时执行 styles 与 scripts;发现这两个任务都有依赖,所以首先需要执行它们的每个依赖;当意识到它们都同时依赖 clean 任务时,Gulp 会确保在回到 styles 与 scripts 任务之前它只执行一次。 这是一个非常有用的特性!然而不幸的是,这并不能转移到新的执行方法中。如果你天真地像我在下面的例子做的一样只做一些简单的改变,那 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 并不指定依赖;它们只是简单地把多个函数组合成一个函数。所以我们需要把依赖从任务中抽离出来,并在一个更大的“父级”任务中把依赖指明成一个 series。
重要提示:你不能在定义其他更小的任务的复合任务之前定义 default 任务。当你调用 gulp.series(“taskName”) 时,名为 “taskName” 需已被定义。这就是为什么在 Gulp 3 中我们可以把 default 放在任意位置,而在 Gulp 4 中却得把 default 移至最底端的原因。
// 这些任务不再有任何依赖
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')));
这当然就意味着,在完成先决条件 clean 之时,你不能直接单独地调用 styles 与 scripts 任务。然而,由于这种机制的建立,可能 clean 会删除掉 scripts 与 styles 的构建区域,所以我也不确定你是不是可以单独地调用它们。
支持异步任务
在 Gulp 3 中,如果在一个任务函数里你的代码是同步的,那你无需再做其他的事。不过在 Gulp 4 中发生了改变:现在你需要使用 done 回调(我将马上提及)了。同时,对于异步的任务,你有 3 个可供选择的方法来确认 Gulp 能够识别你的任务何时完成。它们就是:
1)回调
你可以提供为你的任务函数提供一个回调参数,然后在任务完成时调用这个回调:
var del = require('del');
gulp.task('clean', function(done) {
del(['.build/'], done);
});
2) 返回一个流(Stream)
你也可以返回一个 steam,通常可通过 gulp.src 实现,或直接使用 vinyl-source-stream 这个包。这可能是做这件事最常用的方法。
gulp.task('somename', function() {
return gulp.src('client/**/*.js')
.pipe(minify())
.pipe(gulp.dest('build'));
});
3)返回一个 Promise
Promises 已经成长得越来越卓越了,而且现在甚至已经在 Node 中原生实现了。所以这是一个很有帮助的选择。直接返回一个 Promise,那么 Gulp 就可以知道它什么时候会结束了:
var promisedDel = require('promised-del');
gulp.task('clean', function() {
return promisedDel(['.build/']);
});
支持异步任务的新方式
由于 Gulp 对 async-done 的使用以及其最新的更新,我们有了更多通知异步任务完成的时刻的方式。
4)返回子进程
现在你可以新建子进程然后直接返回他们!如果你并不狂热于加载有很多命令的 package.json 文件或使用大量可能过时的封装这些命令的 gulp 插件,你完全可以利用子进程把你的 npm scripts 移到 Gulp 中。尽管这看起来有点反模式,但也还有其他的方法也能把这件事做得很好。
var spawn = require('child_process').spawn;
gulp.task('clean', function() {
return spawn('rm', ['-rf', path.join(__dirname, 'build')]);
});
5)返回 RxJS 观察对象(observable)
我从未用过 RxJS,它似乎有些小众,但对那些深爱这个库的人来说,你可能对能直接返回一个 observable 感到非常满意!
var Observable = require('rx').Observable;
gulp.task('sometask', function() {
return Observable.return(42);
});
监听
用于监听文件系统并对改变作出反应的 API 也有了一些改变。之前,在传入一个 glob 模式并传入一些可选的选项之后,你能再传入一个任务数组,或者一个接受事件数据的回调函数。因为现在任务是由 series 或 parallel 指定的,它们只简单地返回一个回调函数,然而并没有能够从回调函数中区分不同任务的方法,所以,开发团队把带有回调函数的监听函数签名移除了。像之前一样,gulp.watch 现在将会返回一个“监听者(watcher)”对象作为代替,你可以为其绑定监听器:
// 旧版本
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 事件的处理函数中看到的,你 可能 还会接收到一个文件数据 (file stats) 对象。这个对象只在其可提供时才会出现(并不确定它什么时候它可被提供),但如果你希望它一直出现,你可以设置 alwaysStat 选项的值为 true。Gulp 内部正在使用 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` 和 `parallele` 来复合任务
gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles)));
// 把独立的任务变为独立的函数
function styles() {...}
function scripts() {...}
function clean() {...}
有一些事情需要注意:
- 感谢声明提升机制,这些函数能在 default 任务定义之后才被定义。不像之前使用任务来复合那样,这些任务必须被提前定义。这允许你能把实际上运行的任务函数放在文件顶端,这样别人能更容易找到。而不用先定义一些碎片任务,然后把实际运行的任务函数一团乱麻地放在文件底部。
- styles,scripts 和 clean 现在是“私有”的任务了,所以它们不能使用 Gulp 的命令行运行。
- 数不再匿名。
- 不再把任务名包裹在引号里,这也意味着现在你在使用的是一个代码编辑器 / IDE 能够识别并能得知其是否未定义或者误拼的标识符作为任务名,这样你再也不用在运行 Gulp 时才能知道这个错误了。
- 现在这些“任务”能被分离多个文件里,并能轻易地导入到使用 gulp.task 来定义实际运行任务的文件中。
- 这些任务可独立地测试(如果你觉得需要的话),而无需使用 Gulp。
当然,如果你想让它们在命令行可运行,第 2 点可被修改:
gulp.task(styles);
这会让新的名为 styles 的任务可在命令行运行。注意,我并没有指定一个任务名。gulp.task 现在聪明到可以直接从函数中拿到名字了。不过这当然对匿名函数不起作用:如果你试图只把一个匿名函数设为任务却不给其一个名字,Gulp 会报错。
如果你想给函数一个自定义的名字,你可以使用函数的 displayName 属性。
function styles(){...}
styles.displayName = "pseudoStyles";
gulp.task(styles);
现在任务名将会是 “pseudoStyles” 而不是 “styles” 了。你也可以使用 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)));
// 用 gulp.task 拿到任务
var defaultTask = gulp.task('default');
// 添加描述
defaultTask.description = "Does Default Stuff";
或者让它再短一点,不用额外的变量名:
gulp.task('default', gulp.series(clean, gulp.parallel(scripts, styles)));
gulp.task('default').description = "Does Default Stuff";
这些描述对那些不熟悉你项目的人的帮助会很大,所以我建议在任何合适的地方使用它:它有时比普通的注释更有用、更易得。最后,下面是我推荐的 Gulp 4 最佳实践的模式:
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
不仅你的描述在起重要作用,组成任务的函数名也能提供很多信息。
本篇指南帮助你顺利迁移至 Gulp 4。