Gulp4基本使用及核心原理

12/26/2020 前端工程化Gulp
案例

特点就是高效、易用。使用Gulp开发很简单。

# Gulp的使用步骤

  1. 在项目中安装一个Gulp的开发依赖
  2. 在根目录下添加一个gulpfile.js文件,用于编写一些需要Gulp自动构建的一些任务
  3. 在命令行中通过cli去运行这些任务

# Gulp的基本使用

# 安装

npm i gulp
1

# 起步

  1. 创建一个gulpfile.js的文件,这是个gulp的入口文件
  2. 在文件中创建任务

PS: 在最新的gulp中,取消了同步代码模式,约定每一个任务都是异步任务,当任务完成过后要标记任务完成。否则会报错

// gulpfile.js
exports.foo = () => {
  console.log("foo task working~")
}

// 运行的时候虽然正常输出但是会报错,是否忘记添加结束?
// Starting 'foo'...
// foo task working~
// The following tasks did not complete: foo
// Did you forget to signal async completion?
1
2
3
4
5
6
7
8
9
10

添加参数done表示任务结束

// gulpfile.js
exports.foo = done => {
  console.log("foo task working~")
  done() // 标识任务完成
}
1
2
3
4
5
  1. 命令行运行
gulp foo
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'foo'...
# foo task working~
# Finished 'foo' after 2.43 ms
1
2
3
4
5

# 默认任务

exports.default = done => {
  console.log("default task working~")
  done()
}
1
2
3
4

运行的时候不需要指定任务

gulp    
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'default'...
# default task working~
# Finished 'default' after 3.03 ms
1
2
3
4
5

# gulp4.0之前的任务注册

gulp4.0以前,我们注册任务需要在gulp模块的一个方法中实现

const gulp = require("gulp")

gulp.task('bar', done => {
  console.log('bar working~')
  done()
})
1
2
3
4
5
6

虽然4.0之后还可以使用,但是这种方式已经不被推荐了。更推荐大家使用导出函数成员的方式去定义gulp任务。

# Gulp的组合任务

gulp模块中的series, parallelAPI可以轻松创建组合任务分别执行串行任务并行任务

// 引入串行并行方法
const {series, parallel} = require("gulp")

// 组合任务
const task1 = done => {
  setTimeout(() => {
    console.log("task1 working!")
    done()
  }, 1000)
}

const task2 = done => {
  setTimeout(() => {
    console.log("task2 working!")
    done()
  }, 1000)
}

const task3 = done => {
  setTimeout(() => {
    console.log("task3 working!")
    done()
  }, 1000)
}

// 串行的任务结构
// 接收任意个数的参数,每一个参数都是一个任务,按照顺序依次执行
exports.hello1 = series(task1, task2, task3)

// 并行的任务结构
exports.hello2 = parallel(task1, task2, task3)
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

命令行运行

gulp hello1
# 可以看到先执行了task1结束后才执行task2,task2结束后才执行task3
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'task1'...
# task1 working!
# Finished 'task1' after 1.01 s
# Starting 'task2'...
# task2 working!
# Finished 'task2' after 1 s
# Starting 'task3'...
# task3 working!
# Finished 'task3' after 1.01 s
# Finished 'hello1' after 3.08 s

gulp hello2
# 可以看到task1,task2,task3同时开始执行
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'hello2'...
# Starting 'task1'...
# Starting 'task2'...
# Starting 'task3'...
# task1 working!
# Finished 'task1' after 1.01 s
# task2 working!
# Finished 'task2' after 1.02 s
# task3 working!
# Finished 'task3' after 1.02 s
# Finished 'hello2' after 1.02 s
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

# 用途

  • 例如部署项目,先需要执行编译任务,就需要串行任务。
  • 例如cssjs的编译和压缩,彼此互不干扰,就可以使用并行任务。

# Gulp的异步任务

# 回调方式去解决

# 1. 普通回调
exports.callback = done => {
  console.log("callback task~")
  done()
}
1
2
3
4

这种回调方式和node的是一种,都是错误优先的方式,如果我们要返回一种错误的回调,需要在done中传参数

exports.callback_error = done => {
  console.log("callback error task~")
  done(new Error('task failed~'))
}
1
2
3
4

这个时候运行观察会报出错误,而且之后的任务都不会再继续执行。

gulp callback_error

# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'callback_error'...
# callback error task~
# 'callback_error' errored after 2.38 ms
# Error: task failed~
#     at exports.callback_error (E:\professer\Gulp\gulpfile.js:51:8)
#     at callback_error (E:\professer\Gulp\node_modules\undertaker\lib\set-task.js:13:15)
#     at bound (domain.js:427:14)
#     at runBound (domain.js:440:12)
#     at asyncRunner (E:\professer\Gulp\node_modules\async-done\index.js:55:18)
#     at processTicksAndRejections (internal/process/task_queues.js:79:11)
1
2
3
4
5
6
7
8
9
10
11
12
13
# 2. promise

gulp任务还支持接受promise

exports.promise = () => {
  console.log("promise task~")
  // 这里通过promise的resolve方法返回一个成功的promise
  // 一旦resolve了,那么任务就结束了
  // resolve里面不需要传参数,因为gulp会忽略这个值
  return Promise.resolve()
}
1
2
3
4
5
6
7

promise失败怎么办?reject方法就可以

exports.promise_error = () => {
  console.log("promise task~")
  return Promise.reject(new Error('promise failed'))
}
1
2
3
4
# 3. async/await

ES7中的asyncawait也可以

PS:如果node版本是8+就可以使用这种方式

const delay = time => {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time)
  })
}
exports.async = async () => {
  await delay(1000)
  console.log("async task~")
}
1
2
3
4
5
6
7
8
9

执行

gulp async
# Using gulpfile E:\professer\Gulp\gulpfile.js
# Starting 'async'...
# async task~
# Finished 'async' after 1.01 s
1
2
3
4
5

# stream

需要在任务函数中返回一个stream对象

const fs = require("fs")
exports.stream = () => {
  // 读取文件的文件流对象
  const readStream = fs.createReadStream('package.json')
  // 写入文件的文件流对象
  const writeStream = fs.createWriteStream('temp.txt')
  // 文件复制从读入通过管道倒入到写入里面
  readStream.pipe(writeStream)
  // 把readStream返回
  return readStream
}
1
2
3
4
5
6
7
8
9
10
11

整个任务完成的时机就是stream对象end的时候。因为stream对象都有一个end事件,文件流读取完成过后,end事件就会执行。gulp就会知道任务已经完成了。

类似于

exports.end = done => {
  const readStream = fs.createReadStream('package.json')
  const writeStream = fs.createWriteStream('temp.txt')
  readStream.pipe(writeStream)
  // 监听了end事件执行done
  readStream.on('end', () => {
    done()
  })
}
1
2
3
4
5
6
7
8
9

# Gulp构建过程核心工作原理

Gulp是一个基于流的构建系统。
工作原理 就是把文件读取出来,做完操作之后写入到另一个文件中。

const fs = require("fs")
const { Transform } = require('stream')

exports.default = () => {
  // 文件读取流
  const read = fs.createReadStream('normalize.css')
  // 文件写入流
  const write = fs.createWriteStream('normalize.min.css')
  // 文件转换流
  const transform = new Transform({
    transform: (chunk, encoding, callback) => {
      // ★★★核心转换过程实现★★★
      // chunk => 读取流中读取到的内容(Buffer)
      // 使用toString将Buffer数组转化成字符串
      const input = chunk.toString()
      // 替换掉空白字符和css注释
      // 将转换后的结果放在output变量中
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      // 执行callback的时候返回,错误优先第一个传错误参数,没有的话传null
      // output是成功之后作为结果导出
      callback(null, output)
    }
  })
  
  // 文件复制从读入通过管道先转换,后倒入到写入里面
  read
    .pipe(transform)
    .pipe(write)
    
  return read
}
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

# Gulp读取流和写入流的API

Gulp中专门提供了读取流和写入流的API,比nodeAPI更加强大,更容易使用。转换流大都是通过独立的插件来提供。

举个栗子:

  1. 加载一个压缩css的插件npm install gulp-clean-css和修改文件类型的插件npm install gulp-rename
  2. gulpfile.js里面写
const { src, dest} = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')

exports.default = done => {
  // 读取流,参数是文件路径,比较强大的地方是这里可以使用通配符匹配多个文件
  src('src/*.css')
    // 压缩代码
    .pipe(cleanCss())
    // 修改文件类型
    .pipe(rename({ extname: '.min.css'}))
    // 输出流,参数是输出路径
    .pipe(dest('dest'))
  done()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 在命令行中运行gulp可以看到对应的文件生成,是压缩过的且名称变成了normalize.min.css
更新时间: 2021-09-15 12:03