Gulp 入门学习
1. 什么是Gulp
Gulp是一款自动化构建工具。怎么理解这句话呢,首先自动化的意思是我们通过编写一些Gulp的配置和任务,Gulp自动帮助我们构建应用。其次构建工具是说Gulp是用来构建项目的,比如对我们前端的JavaScript,CSS,HTML进行打包压缩。从这点来看,它实现的功能是和webpack类似的。那既然有webpack,我们为什么要使用Gulp呢?这是因为,Gulp是侧重流程的,也就是构建的过程。比如,我们需要构建JavaScript和CSS,先构建谁,后构建谁,这个构建的流程我们可以自定定义的。而webpack,它的定位是静态模块打包器,如果从这块来区分的话,那么Gulp就是动态构建工具。
2. 如何使用Gulp
##### 2.1 Gulp使用流程
安装Gulp
--------> 创建gulpfile文件
--------> 编写gulpfile.js文件
--------> 运行gulp命令
我们构建任务和流程都是定义在gulpfile文件中的,在我们写好gulpfile文件后,就可以通过gulp命令来执行任务了。
2.2 安装Gulp
安装gulp我们使用如下命令:
npm install --save-dev gulp
安装之后可以在package.json文件中,指定脚本,执行gulp命令:
如果不通过npm脚本来执行gulp,这个时候再命令行中使用gulp命令,会提示命令找不到,是因为我们没有全局安装gulp,而package.json文件里面的脚本,会自动查找当前项目中*node_modules/.bin/*里面的命令,所以可以自动执行。
如果想要全局使用gulp的话,执行以下命令,全局安装gulp即可:
npm install -g gulp
当然我们也可以安装gulp的客户端工具,使用方式是一样的。
npm install -g gulp-cli
到此,我们的安装完成了,可以创建gulpfile.js配置文件,创建自己的任务了。
2.3 如何创建gulpfile文件
2.3.1 第一种方式
在项目的根目录下面直接创建一个___gulpfile.js___,然后直接在这里面进行任务和流程的编写。执行gulp命令的时候会自动查找到这个文件。
2.3.1 第二种方式
在项目的根目录下面,创建一个名为___gulpfile.js___的___文件夹___,在文件夹里面创建一个___index.js__,这样的话,我们直接执行gulp也会找到这个配置文件。
2.4 编写gulpfile.js文件
在gulpfile.js中,我们需要编写的就是我们需要执行的任务。对于gulp来说,我们的任务,就是一个一个的JavaScript异步函数。这个任务函数可以接受一个回调函数作为参数,这个回调函数是有gulp在执行任务函数的时候传递进来的。当我们的任务函数执行之后,我们需要通知gulp当前的任务是执行成功了,还是执行失败了,gulp根据任务函数的通知来决定是否继续执行或者结束。所以,我们要在任务中返回stream、promise、event emmitter、child process或者ovservable这些类型的值,如果我们不返回这些值,那么必须要执行gulp给任务函数传递的回调函数,否则,gulp则获取不到任务函数的执行结果。
__注意:既然我们的任务都是函数,那么我们就可以在函数中做任何事情。我们既可以写一般的JavaScript代码,也可以在函数中使用第三方的模块。当然,我们也可以使用Gulp提供的插件。
我们一起来看个例子,例子的文件名是gulpfile.js,是我们的gulp配置文件:
const {src, dest} = require('gulp');
/**
* 这个函数返回stream类型的值
*/
function streamTask() {
return src('./test.js').pipe(dest('output'));
}
/**
* 这个函数既没有返回stream、promise、event emmitter、child process或者ovservable这些类型的值
* 也没有调用gulp传入的回调函数
*/
function streamTaskWithoutReturn() {
src('./test.js').pipe(dest('output'));
}
/**
* 这个函数执行了gulp传入进来的回调函数
* @param {*} cb
*/
function showCallBack(cb) {
console.log("gulp传递进来的回调函数:" + cb.toString());
cb();
}
/**
* 导出了streamTask任务和streamTaskWithoutReturn任务
* 他们可以在gulp --task中被展示出来
* 可以使用gulp来执行默认的default任务
* 可以使用gulp noBack 来执行对应的任务
*/
exports.default = streamTask;
exports.noBack = streamTaskWithoutReturn;
在这个例子中我们定义了三个任务(Task),每个任务的写法都不太一样。
在第一个任务streamTask中,我们使用了gulp提供的api:src、dest,这两个api会在后面解释,我们重点关注一下任务的返回值,和接收的回调函数。
对于streamTask这个任务,我们在文件底部,通过exports.default将它设置为gulp默认的任务来执行,此时我们在命令行运行gulp命令,它将被执行:
对于streamTaskWithoutReturn这个任务,我们将它导出为noBack这个名称的任务,我们可以看到它并没有返回stream、promise、event emmitter、child process或者ovservable这些类型之一,也没有接收和使用callback函数,执行的结果如下:
这个时候,gulp提醒我们noBack这个任务并没有完成,并且提醒我们是否忘记在异步完成的时候发信号了。像这种情况,gulp就不知道我们的任务是否完成了。所以我们在编写任务的时候,一定要返回stream、promise、event emmitter、child process或者ovservable这些类型之一,或者接收并使用callback函数了,就像streamTask或者showCallBack函数一样。
对于第三个任务,showCallBack,我们可以看到,它处理了回调函数,但是并没有在文件底部导出这个任务,这个时候就不能通过*gulp [taskName]*的方式去执行这个任务了。
如果我们去执行gulp showCallBack,gulp会提示我们,showCallBack这个任务没有被定义。
如果我们在文件底部,导出这个任务,像这样:
那么,我们就可以通过gulp showCallBack命令去执行这个任务了:
我们可以看到,任务正常执行了,并且输出了cb这个回调函数,这个函数就是gulp在执行任务的时候给我们传递的回调函数,它会移除监听,并做退出操作,这样gulp就能正常执行任务,并且可以获取任务的执行状态了。像上面这个例子,没有被导出的任务叫做__私有任务__,只能够在gulpfile.js里面使用,不能被外部使用,而通过exports导出的任务叫做__公开任务__,可以直接被gulp使用。
至此,我们应该学会了如何编写gulp的单独任务。针对以上的返回类型,需要提及一下,async await函数。我们的任务也可以通过async await函数去编写,因为它会用promise对任务进行包装。大家对以上返回的类型,可能不都了解,等我吗学习完Gulp的API我们就理解了。
我们说gulp在做构建的时候更偏向于流程,接下来,我们看看gulp是如何控制构建流程的。
如果我们有很多个零散的任务,我们想控制执行方式,应该怎么处理呢?这个时候我们就需要使用Gulp提供的两个非常强大的方法:series() 和parallel()。为什么说强大呢,因为这两个方法可以将多个独立的任务组合成一个更大的操作。
series 和 parallel的用法就是如此,我们还可以,任意自由组合使用这两个方法,例如:
gulpfile.js的编辑,基本就是上述的过程,接下来我们看看,_Gulp___给我们提供了哪些___API。
2.5 Gulp的API
在了解API之前,我们先来了解一些概念:
-
Glob glob就是一个字符串,这个字符串是用来匹配路径的。
在glob中可以使用的字符:
* / \\ ** !
我们先说一下什么是字符串片段:
"hello/zero/world"
上面的字符串中,hello、zero、world,这三个就是字符串片段,就是字符串中用/分隔开的字符串。
*: 匹配的是 ___单个字符串片段___中的任意字符,例如:
"src/*.js"
/:是分隔符号,用来表示分隔的。
\\ :是转义字符的意思,如:"hello_\\*_zero",这里面的\\ 将*转义为字符串"*",那么"*"将只会匹配"*"字符
**:在多个字符串片段中匹配任意数量的字符。例如
"scripts/**/*.js"
上述的例子会匹配scripts/index.js、scripts/nested/index.js、scripts/nested/twice/index.js
!:由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的,所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面。第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分。如果取反 glob 只是由普通字符组成的字符串,则执行效率是最高的。例如:
["script/**/*.js", "!script/vendor/"]
总结:glob就是指上述我们示例中的字符串。
-
Vinyl
虚拟的文件格式。它是用来描述文件的(包括文件路径,内容等其他数据)。我们对文件的读取和写入都是通过这个东西来完成的。
-
Vinyl 适配器
Vinyl 适配器就是给我提供了两个方法,分别用于创建读取文件Vinyl描述的流,创建将VInyl写入文件系统的流:
src(globs, [options]):简单的理解,就是这个方法用于读取文件
dest(folder, [options]):简单的理解,就是这个方法用于写入文件
-
src():
用于创建读取从文件系统中读取Vinyl的流,这个流可以在pipe中流动。换句话说,src返回的流可以读取文件的Vinyl的描述。
src(globs, [options])
-
dest():
用于创建一个将Vinyl对象写入文件系统的流。换句话说,dest返回的流可以将Vinyl写入文件系统。
dest(directory, [options])
-
symlink():
创建一个流,用于连接VInyl对象和文件系统。也就是说,用这个流可以操作文件系统。
symlink(directory, [options])
-
lastRun():
检索在当前运行进程中成功完成任务的最后一次时间。最有用的后续任务运行时,监视程序正在运行。当监视程序正在运行时,对于后续的任务运行最有用。
当与
src()
组合时,通过跳过自上次成功完成任务以来没有更 改的文件,使增量构建能够加快执行时间。lastRun(task, [precision])
-
series():
将任务函数或操作,组合成更大的任务。组成大任务的小任务,将依次按顺序执行。
series(...tasks)
-
parallel():
将任务函数或操作,组合成更大的任务。组成大任务的小任务,将同时执行。
parallel(...tasks)
-
watch():
监听globs(匹配到的文件),并在发生更改时运行任务。
watch(globs, [options], [task])
-
registry():
允许将自定义的注册表插入到任务系统中,以期提供共享任务或增强功能。
注意: 只有用
task()
方法注册的任务才会进入自定义注册表中。直接传递给series()
或parallel()
的任务函数(task functions)不会进入自定义任务注册表 - 如果你需要自定义注册表的行为,请通过字符串引用的方式将任务(task)组合在一起。分配新注册表时,将传输当前注册表中的每个任务,并将用新注册表替换当前注册表。这允许按顺序添加多个自定义注册表。
registry([registryInstance])
示例一下:
const { registry, task, series } = require('gulp'); const FwdRef = require('undertaker-forward-reference'); registry(FwdRef()); task('default', series('forward-ref')); task('forward-ref', function(cb) { // body omitted cb(); });
-
tree():
获取当前任务依赖树。
tree([options])
options包含一个属性:deep ,这个属性是boolean类型的数据,默认是false。
tree({deep: true})
-
Vinyl:
虚拟的文件格式。就是用来描述文件的,它会包含文件的路径、内容等其他数据。
new Vinyl([options])
options的属性值如下:
类型 默认值 string process.cwd string string ory array object ents ReadableStream、Buffer、null -
Vinyl.isVinyl():
用于检测一个对象是否是Vinyl的示例。
Vinyl.isVinyl(file);
-
Vinyl.isCustomProp():
确定一个属性是否由 Vinyl 在内部进行管理。Vinyl 在构造函数中设置值或在
clone()
实例方法中复制属性时使用。Vinyl.isCustomProp(property)
参数property的类型是string,是要检查的属性名。
总结:到目前为止,我们已经把Gulp这个工具的使用过程和方式已经介绍完毕了,大家可以在自己的项目中使用Gulp技术了。
不同的技术适应不同的场景,我们考虑是否要使用一个技术,则需要看我们的应用场景是什么。不是说,一味地使用更多的技术就是好的。比如webpack、gulp,他们的适用场景就不一样,我们需要打包模块化的包,则可以选择webpack,我们希望控制构建流程则可以选择Gulp,当然我们也可以将webpack和Gulp技术组合使用,前提是看我们的应用场景是什么。
-