基本介绍

一个工具包,可以帮你自动化和增加你的工作流

gulp 的核心理念是 task runner

  • 可以定义自己的一系列任务,等待任务被执行
  • 基于文件 Stream 的构建流
  • 我们可以使用 gulp 的插件体系来完成某些任务

webpack 的核心理念是 module bundler

  • webpack 是一个模块化的打包工具
  • 可以使用各种各样的 loader 来加载不同的模块
  • 可以使用各种各样的插件在 webpack 打包的生命周期完成其他的任务

gulp 相对于 webpack 的优缺点:

  • gulp 相对于 webpack 思想更加的简单、易用,更适合编写一些自动化的任务
  • 但是目前对于大型项目(Vue、React、Angular)并不会使用 gulp 来构建,比如默认 gulp 是不支持模块化的

基本使用

npm install gulp

每个 gulp 任务都是一个异步的 JavaScript 函数

  • 此函数可以接受一个 callback 作为参数,调用 callback 函数那么任务会结束
  • 或者是一个返回 stream、promise、event emitter、child process 或 observable 类型的函数

任务可以是 public 或者 private 类型的:

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

补充:gulp4 之前, 注册任务时通过 gulp.task 的方式进行注册的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const gulp = require("gulp");
const foo = (cb) => {
console.log("第一个gulp任务");
// 结束任务
cb();
};
/**以前的版本 */
gulp.task("foo2", (cb) => {
console.log("第二个gulp任务");
// 结束任务
cb();
});
module.exports = {
foo,
};

执行任务 npx gulp foo

默认任务

1
2
3
4
module.exports.default = (cb) => {
console.log("默认任务执行");
cb();
};

npx gulp 就会执行默认任务

任务组合 series 和 parallel

series():串行任务组合;

pparallel():并行任务组合;

他们都可以接受任意数量的任务函数或者已经组合的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const foo3 = (cb) => {
setTimeout(() => {
console.log("定时器任务1");
cb();
}, 2000);
};

const foo4 = (cb) => {
setTimeout(() => {
console.log("定时器任务2");
cb();
}, 1000);
};

const seriesTask = series(foo3, foo4);
const parallelTask = parallel(foo3, foo4);

module.exports = {
seriesTask,
parallelTask,
};

读取和写入文件

1
2
//文件读取写入
const copyFile = () => src("./src/*.js").pipe(dest("./dist"));

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

  • 由 src() 产生的流(stream)应当从任务(task 函数)中返回并发出异步完成的信号

dest() 接受一个输出目录作为参数,并且它还会产生一个 Node 流(stream),通过该流将内容输出到文件中

流(stream)所提供的主要的 API 是 .pipe() 方法,pipe 方法的原理是什么呢?

  • pipe 方法接受一个 转换流(Transform streams)或可写流(Writable streams);
  • 那么转换流或者可写流,拿到数据之后可以对数据进行处理,再次传递给下一个转换流或者可写流

glob 文件匹配

src() 方法接受一个 glob 字符串或由多个 glob 字符串组成的数组作为参数,用于确定哪些文件需要被操作。

glob 或 glob 数组必须至少匹配到一个匹配项,否则 src() 将报错;

glob 的匹配规则如下:

  • (一个星号*):在一个字符串中,匹配任意数量的字符,包括零个匹配
  • (两个星号**):在多个字符串匹配中匹配任意数量的字符串,通常用在匹配目录下的文件

(取反!):

  • 由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的
  • 所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面
  • 第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分
1
["src/**/*.js", "!src/main"];

文件转换

使用插件可以对文件进行修改 比如转 ES5 压缩

npm i 插件

1
2
3
4
5
6
7
8
9
10
11
const babel = require("gulp-babel");
const terser = require("gulp-terser");
//文件读取写入
const copyFile = () =>
src("./src/*.js")
// 转为ES5
.pipe(babel({ presets: ["@babel/preset-env"] }))
// 压缩
// .pipe(terser({ mangle: { toplevel: true } }))
.pipe(terser({ toplevel: true }))
.pipe(dest("./dist"));

watch 监听

1
2
3
4
5
6
7
8
9
10
const copyFile = () =>
src("./src/*.js")
// 转为ES5
.pipe(babel({ presets: ["@babel/preset-env"] }))
// 压缩
.pipe(terser({ mangle: { toplevel: true } }))
// .pipe(terser({ toplevel: true }))
.pipe(dest("./dist"));

watch("./src/**/*.js", copyFile);

这样 当 src 下的 js 文件改变时就会自动触发任务

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const { src, dest, parallel, series } = require("gulp");
const htmlmin = require("gulp-htmlmin");
const less = require("gulp-less");
const babel = require("gulp-babel");
const terser = require("gulp-terser");
const inject = require("gulp-inject");

const htmlTask = () =>
src("./src/**/*.html")
// Html压缩
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(dest("./dist"));
const jsTask = () =>
src("./src/**/*.js")
.pipe(babel({ presets: ["@babel/preset-env"] }))
.pipe(terser({ mangle: { toplevel: true } }))
.pipe(dest("./dist"));
const lessTask = () => src("./src/**/*.less").pipe(less()).pipe(dest("./dist"));
// 注入
const injectTask = () =>
src("./dist/**/*.html")
.pipe(
inject(src(["./dist/**/*.js", "./dist/**/*.css"]), { relative: true })
)
.pipe(dest("./dist"));
const buildTask = series(parallel(htmlTask, jsTask, lessTask), injectTask);
module.exports = { buildTask };

本地服务器监听

pnpm add browser-sync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const browserSync = require("browser-sync");
// 开启一个本地服务器
const bs = browserSync.create();
const serverask = () => {
watch("./src/**", buildTask);
bs.init({
port: 8081,
open: true,
// 监听文件变化
files: "./dist",
server: {
baseDir: "./dist",
},
});
};

最终版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const { src, dest, parallel, series, watch } = require("gulp");
const htmlmin = require("gulp-htmlmin");
const less = require("gulp-less");
const babel = require("gulp-babel");
const terser = require("gulp-terser");
const inject = require("gulp-inject");
const browserSync = require("browser-sync");
// html压缩
const htmlTask = () =>
src("./src/**/*.html")
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(dest("./dist"));
// js压缩
const jsTask = () =>
src("./src/**/*.js")
.pipe(babel({ presets: ["@babel/preset-env"] }))
.pipe(terser({ mangle: { toplevel: true } }))
.pipe(dest("./dist"));
// less处理
const lessTask = () => src("./src/**/*.less").pipe(less()).pipe(dest("./dist"));
// 注入js css到index.html
const injectTask = () =>
src("./dist/**/*.html")
.pipe(
inject(src(["./dist/**/*.js", "./dist/**/*.css"]), { relative: true })
)
.pipe(dest("./dist"));
const build = series(parallel(htmlTask, jsTask, lessTask), injectTask);
// 开启一个本地服务器
const bs = browserSync.create();
const serveTask = () => {
// 监听src源代码修改
watch("./src/**", build);
bs.init({
port: 8081,
open: true,
// 监听dist文件变化
files: "./dist",
server: {
baseDir: "./dist",
},
});
};
const serve = series(build, serveTask);
module.exports = { build, serve };
1
2
3
4
"scripts": {
"build": "gulp build",
"serve": "gulp serve"
},

构建 npm run build

服务 npm run serve