Gulp

是这样的,最近呢在捣鼓一系列升级的问题,刚好就遇到了Gulp从3升到4的过程,所以借机呢稍微深入的了解了一下Gulp,在此做个简单的记录。

Gulp我一开始理解的他就是个样式处理工具,但实际了解下来,才发现它原来跟Grunt一样是个构建工具。

那就开整吧,我按我的节奏来哈:

  1. 了解一些底层,知道是怎么来的。
  2. 了解基本概念,不但要知道是怎么来的,还要知道是干啥的。
  3. 了解语法 ,3->4升级实操

一些底层

Gulp 是基于 Node.js 的项目,它的核心使用的是Node.js四种流中的Transform - 在读写过程中可以修改或转换数据的 Duplex流 。即可读又可写的流,它会对传给它的对象做一些转换的操作。

因为基于Streams,而Streams和其它数据处理方法相比最大的优点:

  1. 内存效率:我们无需先将大量数据加载到内存中即可进行处理。它就像往河里(Pipe)倒水一样,一桶水你可以分很多次倒。
  2. 时间效率:拥有数据后立即开始处理数据所需的时间大大减少,而不必等到整个有效负载都传输完成才开始处理。还是倒水的例子,你倒入河里(Pipe)马上就流走了不会等着一桶水倒完之后才流走。

Streams的cheatsheet

Duplex:

Streams内部实现流程图:

想了解细节的推荐去看stream-handbookVideo introduction to node.js streams。通过学习之后我真的认为Node的Streams是正儿八经牛X的存在,对我来说简直是一颗遗珠啊。

基本概念

Task

每个 gulp 任务(task)都是一个异步的 JavaScript 函数,此函数是一个可以接收 callback 作为参数的函数,或者是一个返回 stream、promise、event emitter、child process 或 observable (后面会详细讲解) 类型值的函数。

任务(tasks)可以是 public(公开)private(私有) 类型的。

  • 公开任务(Public tasks) 从 gulpfile 中被导出(export),可以通过 gulp 命令直接调用。
  • 私有任务(Private tasks) 被设计为在内部使用,通常作为 series()parallel() 组合的组成部分。

Gulpfile

Gulp 允许你使用现有 JavaScript 知识来书写 gulpfile 文,gulpfile 是项目目录下名为 gulpfile.js (或者首字母大写 Gulpfile.js,就像 Makefile 一样命名)的文件,在运行 gulp 命令时会被自动加载。在这个文件中,你经常会看到类似 src()dest()series()parallel() 函数之类的 gulp API,除此之外,纯 JavaScript 代码或 Node 模块也会被使用。任何导出(export)的函数都将注册到 gulp 的任务(task)系统中。

处理文件

gulp 暴露了 src()dest() 方法用于处理计算机上存放的文件。

src() 接受 glob 参数,并从文件系统中读取文件然后生成一个 Node 流(stream)。它将所有匹配的文件读取到内存中并通过流(stream)进行处理。

流(stream)所提供的主要的 API 是 .pipe() 方法,用于连接转换流(Transform streams)或可写流(Writable streams)。

dest() 接受一个输出目录作为参数,并且它还会产生一个 Node 流(stream),通常作为终止流(terminator stream)。当它接收到通过管道(pipeline)传输的文件时,它会将文件内容及文件属性写入到指定的目录中。gulp 还提供了 symlink() 方法,其操作方式类似 dest(),但是创建的是链接而不是文件( 详情请参阅 symlink() )。

大多数情况下,利用 .pipe() 方法将插件放置在 src()dest() 之间,并转换流(stream)中的文件。

Glob

glob 是由普通字符和/或通配字符组成的字符串,用于匹配文件路径。可以利用一个或多个 glob 在文件系统中定位文件。

*(一个星号):在一个字符串片段中匹配任意数量的字符,包括零个匹配。对于匹配单级目录下的文件很有用。

** (两个星号):在多个字符串片段中匹配任意数量的字符,包括零个匹配。 对于匹配嵌套目录下的文件很有用。请确保适当地限制带有两个星号的 glob 的使用,以避免匹配大量不必要的目录。

! (取反):由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的,所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面。第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分。如果取反 glob 只是由普通字符组成的字符串,则执行效率是最高的。

插件

Gulp 插件实质上是 Node 转换流(Transform Streams),它封装了通过管道(pipeline)转换文件的常见功能,通常是使用 .pipe() 方法并放在 src()dest() 之间。他们可以更改经过流(stream)的每个文件的文件名、元数据或文件内容。

Gulp

Gulp的cheatsheet

Gulp 3 - > 4 实操

3其实算是废弃了,当然还是可以用的,只是不维护了。所以升一升还是极好的。

  1. 现卸载现有的
1
2
npm uninstall gulp --save-dev
npm uninstall gulp -g

你以为升级完之后是这样色儿的

但是一执行是这样的:

1
AssertionError [ERR_ASSERTION]: Task function must be specified

这是因为Gulp4只支持 2 个参数的 gulp.task了,所以就意味着咱们以前的代码得改了,那咱们就有疑问了怎么改呢?先了解下改了什么。

series、parallel

官网说了:Gulp.js 4.0引入了series()和parallel()方法来组合任务:

  • series(…)按指定的顺序一次运行一个任务,并返回一个按给定的任务 / 函数的顺序执行的函数。

  • parallel(…)以任何顺序同时运行任务,并返回一个能并行执行给定的任务/函数的函数

    可见Gulp做出了很大的努力来实现对任务运行方式的更多控制,提供了选择顺序或并行执行任务的能力,避免之前需要添加别的依赖(传统上是使用 run-sequence)或者丧心病狂地手动分配任务执行的依赖。。

所以,如果之前你有这样一个任务:

1
2
3
gulp.task('copy_css', ['clean_temp'], function() {
...
});

它将会变为:

1
2
3
gulp.task('copy_css', gulp.series('clean_temp', function() {
...
}));

当做出这个改变时,不要忘了你的任务函数现在在 gulp.series 的回调函数里。所以你需要在尾部多出来的那个括号。这很容易被忽略。

注意到 gulp.sereisgulp.parallel 会返回函数,所以它们可以被嵌套。当你的任务有多个依赖时,你可能需要经常地嵌套它们。

例如,这个常见的模式:

1
2
3
gulp.task('default', ['copy_css', 'copy_image'], function() {
...
});

将会变为:

1
2
3
gulp.task('default', gulp.series(gulp.parallel('copy_css', 'copy_image'), function() {
...
}));

依赖问题

这是一个坑点,在 Gulp 3 中,如果你为多个任务指定了同一个依赖,并且它们都在运行时,Gulp 会意识到它们都依赖相同的任务,然后只执行一次这个被依赖的任务。而在 Gulp 4 中,我们不再指定”依赖”,而是使用 seriesparallel 来组合函数,这导致 Gulp 不能判断哪些任务在当它只应运行一次时会被多次运行。所以我们需要改变我们对依赖的处理方式。我们需要把依赖从任务中抽离出来,并在一个更大的“父级”任务中把依赖指明成一个 series

1
2
3
4
5
6
7
// 这些任务不再有任何依赖
gulp.task('copy_css', function() {...});
gulp.task('copy_image', function() {...});
gulp.task('clean_temp', function() {...});

// default 依赖于 copy_css 与 copy_image
gulp.task('default', gulp.series('clean_temp', gulp.parallel('copy_css', 'copy_image')));

使用普通函数

因为现在每一个任务实际上都只是一个函数,也并没有依赖或者其他特别的东西。所以我们不必每个任务都用 gulp.task 来完成。开始拥抱独立的函数而不用再像之前通过传入 gulp.task 的回调函数来写代码:

1
2
3
4
5
6
gulp.task('copyGlobalImage', function() {...});
gulp.task('copyCss', function() {...});
gulp.task('copyGlobalFont', function() {...});
gulp.task('copyAllImage', function() {...});

gulp.task('copy', series(['copy-static'], gulp.parallel('copyGlobalImage', 'copyCss','copyGlobalFont','copyAllImage')));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//改成以下这样
function themeResourcesCopy(srcPath, destPath, infoMsg) {
let res = null;
for (let i = 0; i < theme.length; i += 1) {
for (let j = 0; j < color.length; j += 1) {
res = gulp
.src(srcPath)
.pipe(info(infoMsg))
.pipe(gulp.dest(`${buildTargetPath + color[j]}/${theme[i]}${destPath}`));
}
}
return res;
}

function copyGlobalImage() {
// 目前不考虑同步情况
const g_image = '_global/image/';
info(`${targetTemp + g_image}**`);
return themeResourcesCopy(`${targetTemp + g_image}**`, '/image/', DIS.image_copy_g);
}
...

// 记得去掉引号
gulp.task('copy', series(['copy-static'], parallel(copyGlobalImage, copyCss, copyGlobalFont, copyAllImage)));

最后

第一次接触所以就这样吧,后续会对这块进行优化,因为我发现之前的同事这块写的太复杂了且没有用一些压缩之类的插件,也是一开始都是大家都是开荒,可能都不是专业的,反正团队里比较open,只要不影响原有的功能,优化没人管。

感谢