Published on

Gulp 使用

Authors
  • avatar
    Name
    Et cetera
    Twitter
Table of Contents

Gulp 是什么

Gulp is a toolkit to automate & enhance your workflow

Gulp 是一个基于流的自动化构建工具包,它可以帮助我们自动化地完成一些重复性的工作,比如压缩、编译、合并等等。

Gulp 对比 webpack

Gulpwebpack 都是前端构建工具,但是它们的定位不同,webpack 是模块打包工具,而 Gulp 是任务执行工具。

Gulp 的核心理念是 task runner:

  • 可以定义自己的一系列任务,等待任务执行

  • 基于文件流的方式,将文件作为输入,经过一系列的处理,输出到指定的位置

  • 可以使用 gulp 的插件体系来完成某些任务

webpack 的核心理念是 module bundler:

  • webpack 是一个模块化的打包工具

  • 可以使用各种各样的 loader 来处理不同类型的模块

  • 可以使用各种各样的 pluginwebpack 不同的打包生命周期来完成各种各样的任务

Gulp 相对于 webpack 的优缺点:

  • Gulp 相对于 webpack 更加简单、灵活、易用,更适合编写一些自动化的任务

  • 但是 Gulp 本身并不支持模块化,需要借助 browserify 或者 webpack 来完成模块化的打包

Gulp 的使用

安装

pnpm add gulp -D

touch gulpfile.js

第一个任务

另外以下都是同步任务,Gulp 是支持将任务定义为异步任务的,而异步任务的完成需要调用 cb 回调函数或者返回一个 Promisestreamevent emitterchile processobservable

// gulpfile.js
const foo = (cb) => {
  console.log('first gulp task')
  cb()
}

// 需要导出任务才能被 gulp 执行
module.exports = { foo }

然后命令行执行 pnpx gulp foo,就可以看到输出了。

[16:59:21] Using gulpfile ~/Web_Project/demo/gulpfile.js
[16:59:21] Starting 'foo'...
first gulp task
[16:59:21] Finished 'foo' after 716 μs

另外补充之前旧版本 Gulp 中编写任务的方式:

const gulp = require('gulp')

gulp.task('foo', (cb) => {
  console.log('first gulp task')
  cb()
})

module.exports = { foo }

Gulp 的转义

  1. 使用 babel 来转义 gulpfile.js:

    pnpm add @babel/register -D
    
    # rename gulpfile.js to gulpfile.babel.js
    
  2. 使用 ts-node 来转义 gulpfile.js:

    pnpm add ts-node -D
    
    # rename gulpfile.js to gulpfile.ts
    
  3. 使用 esm 来转义 gulpfile.js:

    pnpm add esm -D
    
    # rename gulpfile.js to gulpfile.esm.js
    
    // gulpfile.esm.js
    // 然后就支持 es module 语法了
    const foo = (cb) => {
      console.log('first gulp task')
      cb()
    }
    
    // 需要导出任务才能被 gulp 执行
    export default foo
    

    运行 pnpx gulp foo,可以看到输出了:

    [17:23:21] Requiring external module esm
    [17:23:21] Using gulpfile ~/Web_Project/demo/gulpfile.esm.js
    [17:23:21] Starting 'foo'...
    first gulp task
    [17:23:21] Finished 'foo' after 739 μs
    

Gulp task

Gulp 4.0 开始,Gulp 将任务分为 public tasksprivate taskspublic tasks 可以被命令行直接执行,而 private tasks 只能被其他任务调用。

public tasks

gulpfile 中导出的任务都是 public tasks,可以通过 pnpx gulp --tasks 来查看所有的 public tasks:

[17:23:21] Requiring external module esm
[17:23:21] Tasks for ~/Web_Project/demo/gulpfile.esm.js
[17:23:21] ├── foo
[17:23:21] └─┬ default
[17:23:21]   └─┬ <series>
[17:23:21]     └── foo

private tasks

private tasks 被设计为在内部使用,通常作为 series()parallel() 的参数,不会被命令行直接执行,也不会被其他任务直接调用。

// gulpfile.esm.js
import { series } from 'gulp'

// 这里 foo 是 private task,只会被 series 调用
export const foo = (cb) => {
  console.log('first gulp task')
  cb()
}

const bar = (cb) => {
  console.log('second gulp task')
  cb()
}

// export default series(foo, bar)
// 默认任务, 执行 `pnpx gulp` 就会执行
export default (cb) => {
  console.log('default gulp task')
  cb()
}

运行 pnpx gulp --tasks,可以看到输出了:

[17:29:45] Requiring external module esm
[17:29:46] Tasks for ~/Web_Project/demo/gulpfile.esm.js
[17:29:46] ├── foo
[17:29:46] └─┬ default
[17:29:46]   └─┬ <series>
[17:29:46]     ├── foo
[17:29:46]     └── bar

compose tasks

Gulp 提供了 series()parallel() 来组合任务,series() 会按照顺序执行任务,而 parallel() 会并行执行任务。

To have your tasks execute in order, use the series() method.

series() 使用示例:

// gulpfile.esm.js
import { series } from 'gulp'

const foo = (cb) => {
  setTimeout(() => {
    console.log('first gulp task')
    cb()
  }, 1000)
}

const bar = (cb) => {
  setTimeout(() => {
    console.log('second gulp task')
    cb()
  }, 2000)
}

export default series(foo, bar)

运行 pnpx gulp,可以看到输出了:

[17:59:32] Requiring external module esm
[17:59:33] Using gulpfile ~/Web_Project/demo/gulpfile.esm.js
[17:59:33] Starting 'default'...
[17:59:33] Starting 'foo'...
first gulp task
[17:59:34] Finished 'foo' after 1.01 s
[17:59:34] Starting 'bar'...
second gulp task
[17:59:36] Finished 'bar' after 2 s
[17:59:36] Finished 'default' after 3.01 s

For tasks to run at maximum concurrency, combine them with the parallel() method.

parallel() 使用示例:

// gulpfile.esm.js
import { parallel } from 'gulp'

const foo = (cb) => {
  setTimeout(() => {
    console.log('first gulp task')
    cb()
  }, 1000)
}

const bar = (cb) => {
  setTimeout(() => {
    console.log('second gulp task')
    cb()
  }, 2000)
}

export default parallel(foo, bar)

运行 pnpx gulp,可以看到输出了:

[18:00:51] Requiring external module esm
[18:00:51] Using gulpfile ~/Web_Project/demo/gulpfile.esm.js
[18:00:51] Starting 'default'...
[18:00:51] Starting 'foo'...
[18:00:51] Starting 'bar'...
first gulp task
[18:00:52] Finished 'foo' after 1 s
second gulp task
[18:00:53] Finished 'bar' after 2 s
[18:00:53] Finished 'default' after 2 s

对比任务执行结束时间可以看到 series() 是按照顺序执行任务,用了 3s; 而 parallel() 是并行执行任务,用了 2s

嵌套任务

series() and parallel() can be nested to any arbitrary depth.

// gulpfile.js
const { series, parallel } = require('gulp')

function clean(cb) {
  // body omitted
  cb()
}

function cssTranspile(cb) {
  // body omitted
  cb()
}

function cssMinify(cb) {
  // body omitted
  cb()
}

function jsTranspile(cb) {
  // body omitted
  cb()
}

function jsBundle(cb) {
  // body omitted
  cb()
}

function jsMinify(cb) {
  // body omitted
  cb()
}

function publish(cb) {
  // body omitted
  cb()
}

exports.build = series(
  clean,
  parallel(cssTranspile, series(jsTranspile, jsBundle)),
  parallel(cssMinify, jsMinify),
  publish
)

Gulp 插件

Gulp 读写文件

Gulp 提供了 src()dest() 来读写文件。

// gulpfile.esm.js
import { src, dest } from 'gulp'

// src() 和 dest() 返回值都是 ReadWriteStream
const copyFile = () => src('./oor.txt').pipe(dest('dist'))

export default copyFile

Gulp-babel

Gulp 本身不支持 babel,需要借助 gulp-babel 插件来完成 babel 的转义。

pnpm add gulp-babel @babel/core @babel/preset-env -D
// src/main.js
const singer = 'aimyon'

const foo = () => {
  console.log('foo')
}

foo()

通过 .babelrc 配置文件来配置 babel:

{
  "presets": ["@babel/preset-env"]
}
// gulpfile.esm.js
import { src, dest } from 'gulp'
import babel from 'gulp-babel'

const parseJS = () =>
  src('src/*.js')
    .pipe(
      babel()
      // 也可以不配置 .babelrc,直接在这里配置
      // babel('@babel/preset-env')
    )
    .pipe(dest('dist'))

export default parseJS

同样执行一下 pnpx gulp,可以看到输出了:

[21:30:49] Requiring external module esm
[21:30:50] Using gulpfile ~/Web_Project/demo/gulpfile.esm.js
[21:30:50] Starting 'default'...
[21:30:51] Finished 'default' after 752 ms

然后 dist 文件夹下确实也有转义后的文件了。

// dist/main.js
'use strict'

var singer = 'aimyon'
var foo = function foo() {
  console.log('foo')
}
foo()

Gulp-terser

Gulp 本身不支持 terser,需要借助 gulp-terser 插件来完成 terser 的压缩。

pnpm add gulp-terser -D
// gulpfile.esm.js
import { src, dest } from 'gulp'
import babel from 'gulp-babel'
import terser from 'gulp-terser'

const parseJS = () =>
  src('src/*.js')
    // 转义
    .pipe(babel())
    // 压缩
    .pipe(terser())
    .pipe(dest('dist'))

export default parseJS
dist/main.js
// dist/main.js
// 可以看到其实丑化还不完全
"use strict";var singer="aimyon",foo=function(){console.log("foo")};foo();

再修改 gulpfile :

import { src, dest } from 'gulp'
import babel from 'gulp-babel'
import terser from 'gulp-terser'

const parseJS = () =>
  src('src/*.js')
    // 转义
    .pipe(babel())
    // 压缩
    .pipe(terser({ mangle: { toplevel: true } }))
    .pipe(dest('dist'))

export default parseJS
dist/main.js
"use strict";var o="aimyon",n=function(){console.log("foo")};n();

再进行更进一步配置:

// gulpfile.esm.js
import { src, dest } from 'gulp'
import babel from 'gulp-babel'
import terser from 'gulp-terser'

const parseJS = () =>
  src('src/*.js')
    // 转义
    .pipe(babel())
    // 压缩
    // .pipe(terser({ mangle: { toplevel: true } }))
    .pipe(terser({ toplevel: true }))
    .pipe(dest('dist'))

export default parseJS
dist/main.js
"use strict";console.log("foo");

watch

Gulp 提供了 watch() 来监听文件变化,然后执行任务。

import { src, dest, watch } from 'gulp'
import babel from 'gulp-babel'
import terser from 'gulp-terser'

const parseJS = () =>
  src('src/*.js')
    // 转义
    .pipe(babel())
    // 压缩
    .pipe(terser({ mangle: { toplevel: true } }))
    // .pipe(terser({ toplevel: true }))
    .pipe(dest('dist'))

// 监听文件变化
watch('src/**/*.js', parseJS)

export default parseJS

使用 Gulp 构建工程

pnpm add gulp gulp-babel gulp-terser gulp-less gulp-htmlmin gulp-inject browser-sync -D
// gulpfile.esm.js
import { src, dest, watch, series, parallel } from 'gulp'
import babel from 'gulp-babel'
import terser from 'gulp-terser'
import htmlmin from 'gulp-htmlmin'
import less from 'gulp-less'
import inject from 'gulp-inject'
import browserSync from 'browser-sync'

// 1.对html进行打包
const htmlTask = () =>
  src('src/**/*.html')
    .pipe(htmlmin({ collapseWhitespace: true }))
    .pipe(dest('dist'))

const parseJS = () =>
  src('src/*.js')
    // 转义
    .pipe(babel())
    // 压缩
    .pipe(terser({ mangle: { toplevel: true } }))
    // .pipe(terser({ toplevel: true }))
    .pipe(dest('dist'))

// 3.对less进行打包
const lessTask = () => src('src/**/*.less').pipe(less()).pipe(dest('dist'))

// 4.在html中注入js和css
const injectTask = () =>
  src('dist/**/*.html')
    .pipe(inject(src(['dist/**/*.js', 'dist/**/*.css']), { relative: true }))
    .pipe(dest('dist'))

// 5.开启一个本地服务器
const bs = browserSync.create()
const serve = () => {
  watch('src/**', buildTask)

  bs.init({
    port: 8080,
    open: true,
    files: 'dist/*',
    server: {
      baseDir: 'dist',
    },
  })
}

// 创建项目构建的任务
const buildTask = series(parallel(htmlTask, parseJS, lessTask), injectTask)
const serveTask = series(buildTask, serve)
// webpack搭建本地 webpack-dev-server

export default series(buildTask, serveTask)

然后建立根目录下建立 src 文件夹,然后在 src 文件夹下建立 index.htmlindex.jsindex.less

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Gulp Projecct</title>
    <!-- inject:css -->
    <!-- endinject -->
  </head>
  <body>
    <!-- inject:js -->
    <!-- endinject -->
  </body>
</html>
// src/less/style.less
@mainColor: skyblue;

body {
  background-color: @mainColor;
}
// src/index.js
const singer = 'aimyon'

const foo = () => {
  console.log('foo')
}

foo()

最后执行 pnpx gulp,就可以看到效果了。

[22:24:42] Requiring external module esm
[22:24:43] Using gulpfile ~/Web_Project/demo/gulpfile.esm.js
[22:24:43] Starting 'default'...
[22:24:43] Starting 'htmlTask'...
[22:24:43] Starting 'parseJS'...
[22:24:43] Starting 'lessTask'...
[22:24:43] Finished 'parseJS' after 10 ms
[22:24:43] Finished 'htmlTask' after 23 ms
[22:24:43] Finished 'lessTask' after 24 ms
[22:24:43] Starting 'injectTask'...
[22:24:43] gulp-inject 2 files into index.html.
[22:24:43] Finished 'injectTask' after 6.85 ms
[22:24:43] Starting 'htmlTask'...
[22:24:43] Starting 'parseJS'...
[22:24:43] Starting 'lessTask'...
[22:24:44] Finished 'parseJS' after 3.23 ms
[22:24:44] Finished 'lessTask' after 8.31 ms
[22:24:44] Finished 'htmlTask' after 8.84 ms
[22:24:44] Starting 'injectTask'...
[22:24:44] gulp-inject 2 files into index.html.
[22:24:44] Finished 'injectTask' after 7.63 ms
[22:24:44] Starting 'serve'...
[Browsersync] Access URLs:
 ---------------------------------------
       Local: http://localhost:8080
    External: http://192.168.18.161:8080
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 ---------------------------------------
[Browsersync] Serving files from: dist
[Browsersync] Watching files...

使用 Gulp 打包 es 、 commonjs 、 umd 模块

pnpm add gulp @babel/core @babel/preset-env gulp-babel gulp-terser gulp-typescript typescript del @babel/plugin-transform-modules-commonjs -D

# 然后设置 package.json 中 type: module
// 因为现在可以通过 type: module 来使用 es module 语法,所以可以直接使用 es module 语法
// gulpfile.mjs
import gulp from 'gulp'
import babel from 'gulp-babel'

import ts from 'gulp-typescript'
import { deleteAsync as del } from 'del'

const { src, dest, series } = gulp

// 每次构建前先清空之前的构建结果
const clean = async () => {
  await del('es/**')
  await del('lib/**')
  await del('cjs/**')
}

// 从 ts 生成 es 模块
const genES = () => {
  const tsProject = ts.createProject('tsconfig.json', {
    module: 'ESNext',
  })

  return tsProject.src().pipe(tsProject()).pipe(babel()).pipe(dest('src/es/'))
}

// 从 ts 生成 commonjs 模块
const genCJS = () => {
  return src('src/es/**/*.js')
    .pipe(
      babel({
        presets: [['@babel/env']],
        plugins: ['@babel/plugin-transform-modules-commonjs'],
      })
    )
    .pipe(dest('src/lib/'))
}

// 构建声明文件并输出到 es 和 lib 目录下
const genDeclaration = () => {
  const tsProject = ts.createProject('tsconfig.json', {
    declaration: true,
    emitDeclarationOnly: true,
  })

  return tsProject.src().pipe(tsProject()).pipe(dest('src/es/')).pipe(dest('src/lib/'))
}

export default series(clean, genES, genCJS, genDeclaration)