Gulp是一个自动化构建工具,类似Webpack,目的是把开发环境的代码转换为生产环境的代码,比如ES6转ES5、Saas转CSS、文件压缩等。
Gulp的整体设计思路是通过文件读写和一系列Stream插件实现文件内容处理,完成构建工作。完整的构建工作可以拆分为多个构建步骤,每一个构建步骤称之为任务,一个大型的项目构建由多个任务组合完成。
1.Gulp执行依赖Node环境,需要安装Node
2.安装命令行工具gulp-cli
3.安装核心依赖库gulp
4.创建gulpfile.js,在其中书写任务代码
5.导出要执行的任务
6.使用命令行工具执行任务
下面是代码演示:
# 安装node环境
# 安装命令行工具
npm install -g [email protected]
# 安装核心依赖库
npm install -D [email protected]
# 创建gulpfile.js
touch gulpfile.js
# 书写并导出任务,见下文
# 执行任务
gulp xxxx # 导出任务函数名称/default
在gulpfile.js中书写并导出任务:
// vim gulpfile.js
const { src, dest } = require("gulp");
// 任务函数
function copy() {return src("src/index.html").pipe(dest("dist/"));
}
// 导出任务
module.exports = {copy,
};
// gulp copy 执行任务
一个任务就是一个函数,创建任务就是创建一个函数,然后将要执行的函数导出即可。
任务函数的内部通常由一个一个的流处理器构成的流处理管道,起点和终点是文件读写,中间是各种流处理插件,流处理器之间则通过stream.pipe连接,后面我们重点看看这几个部分。
Gulp本身是非常简单的,在流处理器方面只提供了gulp.src和gulp.dest两个函数用于文件读写,中间的处理器全部由外部插件提供,这些插件全部按照Gulp提供的规范编写,可以无缝嵌入Gulp流处理过程。这种基于组合的设计思路使得Gulp本身非常精简,同时也让Gulp的生态越来越丰富。
我个人非常喜欢这种轻量化的设计思路,只需要提供一套规范和主流程框架就可以实现各种功能,同时还能保证项目的健壮性、可维护性和可扩展性。
gulp.src和gulp.dest是一个读写流,继承自stream.Duplex,为什么是读写流而不是一边只读,一边只写呢?因为src和dest不仅用于两端,还会用在中间部分,比如先读一个文件处理一下,然后再读一个文件进行合并压缩等等。
这两个函数的使用也比较简单,最常用的就是像上文copy函数那样开头读一个文件,结尾将文件写入一个目录。
function copy() {return src("src/index.html").pipe(dest("dist/"));
}
src接收一个路径字符串或者路径数组,用于读取一个或者多个文件,而dest则接收一个目录路径字符串,用于将内容写入文件。
src("src/index.html")
src(["src/index.html", "src/index.css"])
dest("dist/")
值得注意的是,这里的路径字符串是Glob路径匹配符而不是普通的字符串,通过Glob的方式可以方便的匹配任意层级、任意格式的文件。
Glob用于匹配文件路径,由普通字符、特殊字符和分隔符三个部分组成,即基本用法如下:
*
: 匹配单级目录下任意数量的字符* **
: 匹配任意级目录下任意数量的字符* !
: 取反,必须在数组中跟在一个正向的glob后面* {}
: 取或,同时匹配多个模式,如{ab,cd}
等接下来我们对特殊字符举例说明:
首先*
通常用来批量匹配文件,比如*.js,*.png
等等,或者dist/*
匹配所有文件。
其次**
通常用来匹配跨越多级目录,比如images/**/*.png
,如果直接写images/**.png
,那大概率只能匹配images单级目录下面的png文件,因为子目录的名字里没有.png
后缀,**
虽然可以跨层级,但也是一级一级往下匹配的,如果中间一级不能匹配上,那就会不会继续向下匹配了。
!
通常用来表示在批量匹配之后排除其中的个别文件,所以需要跟在正向匹配后面,比如["src/*.js", "!src/abc.js"]
。
{}
通常用来匹配多个文件后缀,比如*.{png,jpg}
,注意{png,jpg}
的逗号两边不能有空格。
好了,理解Glob之后那文件读写就变得非常轻松了,接下来就看看如何处理这些流。
在Gulp中,文件流的处理是通过各种流处理插件完成的,这些插件继承自stream.Transform,本质就是一个转换流。不同的插件之间通过stream.pipe()相连,由此构成了一个处理流水线,实现整个构建任务。
Gulp提供了一个插件查询的网址:gulpjs.com/plugins ,我们要做的就是下载这些插件,然后把他放置在需要的pipe()之中进行执行。
src('src/index.html').pipe(插件1).pipe(插件2).pipe(dest("dist/"))
对于前端开发来说,常用的插件如下:
1.HTML相关:* gulp-htmlmin: 压缩HTML文件* gulp-file-include:引入HTML代码片段
2.CSS相关:* gulp-sass/gulp-less: SAAS、LESS转CSS* gulp-autoprefixer: 自动添加厂商前缀* gulp-cssmin: 压缩CSS文件
3.JS相关:* gulp-babel: ES6转ES5* gulp-uglify: 压缩JS文件
4.其它:* gulp-rename: 文件重命名* gulp-concat: 文件合并* gulp-webserver: 搭建本地服务器* webpack-stream: 在gulp中使用webpack
大型项目的构建往往要拆分为多个构建任务,而任务就是gulp的执行单元。任务之间可以通过组合形成新的任务,这种组合方式可以实现任意复杂度的构建流程。
上文我们介绍了一个任务的本质就是pipe连接的stream处理流水线,这边再补充几点关于任务的知识:
1.一个任务就是一个函数。
2.只有导出的任务函数才能被执行,执行命令为gulp [任务函数名称]
。
3.通过exports.default
导出的任务称为默认任务,可以通过gulp
直接执行。
4.任务完成后需要通知Gulp,否则控制台就是报错说没有收到任务结束的通知。对于组合任务来说,通知尤其重要,如果多个任务之间是线性执行的,那么只有当收到前一个任务完成的通知之后,Gulp才会执行下一个任务。任务结束通知有三种方式:* 任务函数可以接收一个cb参数,调用cb回调进行通知* 任务函数返回一个Promise* 任务函数返回一个Stream
接下来我们演示一下这三种任务结束通知方式:
// 执行回调函数
function start(cb) {console.log("task start");cb();
}
// 返回Promise
function clean() {return new Promise((resolve, reject) => {setTimeout(() => {console.log("dir clean finished");resolve();}, 1000);});
}
// 返回Stream
function copy() {return src("src/index.html").pipe(dest("dist/"));
}
当我们需要将多个任务按照顺序进行组合,依次执行或者并行执行时就可以使用组合任务。Gulp提供了series和parallel两个API用于创建组合任务。
series和parallel都会返回一个新的任务,我们可以像使用普通任务那样使用组合任务,两者的基本使用方法如下:
const {series, parallel} = require("gulp");
series(task1, task2, ...);
parallel(task1, task2, ...);
series是线性执行,其中的任务会一个接一个的执行,只有当前面的任务通知Gulp执行完成后,后面的任务才会执行。
paralle是并行执行,任务之间没有先后关系,这种执行方式可以提高整体的构建效率。
在开发过程中,我们希望构建系统能够监听文件的变化自动构建,实现页面的热更新。为此,Gulp提供了一个watch
API实现文件变化的监听,当文件发生变化后,Gulp将自动执行注册的任务。
const { watch } = require("gulp");
function watchJS() {watch('src/index.js', task1);
}
exports.default = watchJS;
最后,我们用一个完整的案例把上面的知识串起来。在这个案例中,我们将监听js模块文件的变化,实现如下几个构建动作:
1.清理dist目录
2.使用gulp-babel将ES6转为ES5
3.使用gulp-concat将多个模块组合为单个文件
4.使用gulp-uglify进行JS文件压缩
5.使用gulp-rename重命名最终生成的JS文件
让我们开始吧!
在src目录下有两个JS模块文件moduleA.js,moduleB.js,我们的目的就是把他打包为module.min.js。
// moduleA.js
const animal = "dog";
// 跑得更快
function run() {console.log("run faster!");
}
// moduleB.js
const animal = "bird";
// 飞得更高
function fly() {console.log("run higher!");
}
1.安装所有需要的插件
npm install -D [email protected] @babel/[email protected] @babel/[email protected]
npm install -D [email protected] [email protected] [email protected]
# 这个不是插件,用于删除文件
npm install -D [email protected]
2.编写gulpfile.js
const { src, dest, series, watch } = require("gulp");
const babel = require("gulp-babel");
const concat = require("gulp-concat");
const uglify = require("gulp-uglify");
const rename = require("gulp-rename");
const del = require("del");
function clean() {// 返回Promisereturn del("dist");
}
function build() {return src("src/module*.js").pipe(babel({ presets: ["@babel/env"] })).pipe(concat("module.js")).pipe(uglify()).pipe(rename({ extname: ".min.js" })).pipe(dest("dist/"));
}
function watchJS() {watch("src/module*.js", series(clean, build));
}
exports.default = watchJS;
// 命令行执行gulp
最终我们构建得到的module.min.js内容为:
"use strict";var animal="dog";function run(){console.log("run faster!")}animal="bird";function fly(){console.log("run higher!")}