逐步详解如何使用 gulp 去进行自动化构建

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逐步详解如何使用 gulp 去进行自动化构建相关的知识,希望对你有一定的参考价值。

在项目开发完,上线前还需要做一系列的准备工作,包括不限于:

  • 压缩 CSS, javascripthtml 文件
  • 如果使用 SASS 或是 LESS 的话,还需要对其进行编译,否则整个项目都无法运行
  • 同理,如果有对 JavaScript 的版本需求,还需要使用 babel 对 JavaScript 进行转译,否则有些功能同样无法实现

所有的流程全都人力完成的话,一来耗时,二来会介入很多人为错误,所以很多的自动化构建工具应运而生。

Gulp 是基于 node.js 的一个前端自动化构建工具,用于处理上文列举的问题。

gulp 的基础使用

通过学习怎么使用单一任务、组合任务、异步操作去了解怎么完成对 gulp 基础使用的学习。

gulp 的单独任务

gulp 的基础使用,这里 gulp 的版本使用的是 v4.0 之后的版本,用的语法也是 ES6 的语法。

  1. 新建一个文件夹并且初始化一个 node 项目结构

    gulp> mkdir gulp-sample
    gulp> cd .\\gulp-sample\\
    gulp-sample> yarn init
    
  2. 下载并安装 gulp 依赖包

    因为 gulp 是在开发环境中用来打包项目的,所以只需要在开发环境中安装即可

    gulp-sample> yarn add gulp --dev
    
  3. 在根目录下新增一个 gulpfile.js

    gulpfile.js 是 gulp 的入口文件,这个是必须的。

    第一步首先尝试在命令行有输出,看看 gulp 是否可以正常运行

    gulpfile.js 内容如下:

    // gulp 的入口文件
    
    exports.default = () => {
      console.log('hello world');
    };
    

    v4.0 后,gulp 通过导出一个函数的方式去定义一个任务,所以这里直接通过 commonJS 导出一个函数即可

    导出名为 default 的表明这是默认执行函数

    注:v3.0 的写法,也就是通过调用 task() 函数传入任务的方式,同样也可以运行,不过使用起来还是箭头函数的写法方便一些

    const gulp = require('gulp');
    
    gulp.task('default', function () {
      console.log('hello world');
    });
    
  4. 运行默认函数

    在终端上运行:

    gulp-sample> yarn gulp
    yarn run v1.22.10
    $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp
    [04:00:14] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
    [04:00:14] Starting 'default'...
    hello world
    [04:00:14] The following tasks did not complete: default
    [04:00:14] Did you forget to signal async completion?
    error Command failed with exit code 1.
    info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    

    [04:00:14] Starting 'default'... 后能看到 hello world 的输出,代表了 gulp 的运行是没有问题的。

    不过在运行完成后还出现了报错信息,说的是:

    The following tasks did not complete: default

    即 default 这个任务并没有完成。

  5. 解决 not complete 报错

    会出现报错是因为 v4.0 后就取消了同步处理机制,所有的函数被当做异步函数进行处理,因此需要传入一个指示去告知 gulp 的任务已经处理好了

    gulp 自己在函数中就包含了这个指示器,调用方法如下:

    exports.default = (done) => {
      console.log('hello world');
    
      done(); // 指示任务完成
    };
    

    再次运行:

    gulp-sample> yarn gulp
     yarn run v1.22.10
     $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp
     [04:19:00] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
     [04:19:00] Starting 'default'...
     hello world
     [04:19:00] Finished 'default' after 2.53 ms
     Done in 1.17s.
    

gulp 的组合任务

主要是通过 gulp 提供的两个函数完成,主体代码为:

const { series, parallel } = require('gulp');

const task1 = (done) => {
  setTimeout(() => {
    console.log('task1 done');
    done();
  }, 2000);
};

const task2 = (done) => {
  setTimeout(() => {
    console.log('task2 done');
    done();
  }, 1000);
};

const task3 = (done) => {
  setTimeout(() => {
    console.log('task3 done');
    done();
  }, 1000);
};

series

串行任务

使用串行任务必须完成先指定的任务,再完成下一个任务。应用场景有:先执行对 SASS 的编译,再部署 CSS 代码

exports.seriesTask = series(task1, task2, task3);

终端运行:

  gulp-sample> yarn gulp seriesTask
  yarn run v1.22.10
  $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp seriesTask
  [05:00:42] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
  [05:00:42] Starting 'seriesTask'...
  [05:00:42] Starting 'task1'...
  task1 done
  [05:00:44] Finished 'task1' after 2.02 s
  [05:00:44] Starting 'task2'...
  task2 done
  [05:00:46] Finished 'task2' after 2 s
  [05:00:46] Starting 'task3'...
  task3 done
  [05:00:48] Finished 'task3' after 2.01 s
  [05:00:48] Finished 'seriesTask' after 6.04 s
  Done in 7.22s.

可以看出是有先后执行顺序的,而且尽管 task1 花费的时间比其他的多——task1 需要等待 2 秒,其他两个等待 1 秒——task1 仍然是最先被执行和完成的。

parrllel

并行任务

使用并行任务互不干扰。应用场景有:可以同时进行对 CSS 和 JavaScript 的编译过程

exports.parallelTask = parallel(task1, task2, task3);

终端运行:

gulp-sample> yarn gulp parallelTask
  yarn run v1.22.10
  $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp parallelTask
  [05:02:23] Using gulpfile C:\\assignmentF\\front\\gulp\\gulp-sample\\gulpfile.js
  [05:02:23] Starting 'parallelTask'...
  [05:02:23] Starting 'task1'...
  [05:02:23] Starting 'task2'...
  [05:02:23] Starting 'task3'...
  task2 done
  [05:02:24] Finished 'task2' after 1.02 s
  task3 done
  [05:02:24] Finished 'task3' after 1.02 s
  task1 done
  [05:02:25] Finished 'task1' after 2.01 s
  [05:02:25] Finished 'parallelTask' after 2.01 s
  Done in 3.31s.

反观 parallel 这里就不一样,task2 和 task3 比 task1 先运行。

gulp 的异步流程处理操作

gulp 升级到了 v4.0 后都是采取的回调函数的方式,异步地调用每个 task。这也就造成了确认操作是否完成成了个问题。下面列举了三个最常用,也是使用 JavaScript 原生操作的解决方式。

使用回调函数

上文已经提到过,gulp v4.0 之后已经取消了同步操作,而是通过传入一个指示器——done,一个回调函数——的方式去接受该任务已经完成的信号。

因此,当出现错误异常时,也可以通过调用 done() 函数,并且中间抛出异常的方式通知 gulp,具体操作与原生 JavaScript 的回调函数一样,都是错误优先的机制。

需要注意的是,一旦抛出异常,也会终止其他的任务继续运行。

const { series, parallel } = require('gulp');

const task1 = (done) => {
  setTimeout(() => {
    console.log('task1 done');
    done();
  }, 2000);
};

const task2 = (done) => {
  setTimeout(() => {
    console.log('task2 done');
    // 修改了这里 ↓
    done(new Error('task failed'));
    // 修改了这里 ↑
  }, 1000);
};

const task3 = (done) => {
  setTimeout(() => {
    console.log('task3 done');
    done();
  }, 1000);
};

exports.seriesTask = series(task1, task2, task3);

exports.parallelTask = parallel(task1, task2, task3);

exports.default = (done) => {
  console.log('hello world');

  done(); // 指示任务完成
};

运行结果:

  • series 报错

    gulp-sample> yarn gulp seriesTask
    yarn run v1.22.10
    $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp seriesTask
    [10:00:12] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
    [10:00:12] Starting 'seriesTask'...
    [10:00:12] Starting 'task1'...
    task1 done
    [10:00:14] Finished 'task1' after 2.01 s
    [10:00:14] Starting 'task2'...
    task2 done
    [10:00:15] 'task2' errored after 1.02 s
    [10:00:15] Error: task failed
        at Timeout._onTimeout (C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js:13:10)
        at listOnTimeout (internal/timers.js:554:17)
        at processTimers (internal/timers.js:497:7)
    [10:00:15] 'seriesTask' errored after 3.03 s
    error Command failed with exit code 1.
    info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    

    可以看到在 task2 这里抛出了异常,task3 没有继续运行

  • parallel 报错

    gulp-sample> yarn gulp parallelTask
    yarn run v1.22.10
    $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp parallelTask
    [10:01:09] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
    [10:01:09] Starting 'parallelTask'...
    [10:01:09] Starting 'task1'...
    [10:01:09] Starting 'task2'...
    [10:01:09] Starting 'task3'...
    task2 done
    [10:01:10] 'task2' errored after 1.02 s
    [10:01:10] Error: task failed
        at Timeout._onTimeout (C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js:13:10)
        at listOnTimeout (internal/timers.js:554:17)
        at processTimers (internal/timers.js:497:7)
    [10:01:10] 'parallelTask' errored after 1.03 s
    error Command failed with exit code 1.
    info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    

    可以看到 task2 这里抛出了异常,task1 和 task3 都没有继续运行

使用 promise

使用 promise 可以解决代码中嵌套过深的方法,具体使用方法就是返回 promise 即可:

const { series, parallel } = require('gulp');

const task1 = (done) => {
  setTimeout(() => {
    console.log('task1 done');
  }, 2000);
  return Promise.resolve('this will be ignored');
};

const task2 = (done) => {
  setTimeout(() => {
    console.log('task2 done');
  }, 1000);
  return Promise.reject(new Error('task2 failed'));
};

const task3 = (done) => {
  setTimeout(() => {
    console.log('task3 done');
    done();
  }, 1000);
};

exports.seriesTask = series(task1, task2, task3);

exports.parallelTask = parallel(task1, task2, task3);

运行结果:

gulp-sample> yarn gulp seriesTask
yarn run v1.22.10
$ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp seriesTask
[10:07:41] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
[10:07:41] Starting 'seriesTask'...
[10:07:41] Starting 'task1'...
[10:07:41] Finished 'task1' after 1.41 ms
[10:07:41] Starting 'task2'...
[10:07:41] 'task2' errored after 1.03 ms
[10:07:41] Error: task2 failed
    at task2 (C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js:14:25)
    at bound (domain.js:413:15)
    at runBound (domain.js:424:12)
    at asyncRunner (C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\async-done\\index.js:55:18)
    at processTicksAndRejections (internal/process/task_queues.js:75:11)
[10:07:41] 'seriesTask' errored after 6.61 ms
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

使用 async/await

既然可以使用 promise,那么也可以使用 ES7 提供的语法糖:async/await,只要 node 环境是 8 以上,就可以使用 async/await,用法如下:

const { series, parallel } = require('gulp');

const timeout = (time) => {
  return new Promise((resolve) => {
    setTimeout(resolve, time);
  });
};

const task1 = async (done) => {
  await timeout(2000);
  console.log('task1 done');
};

const task2 = async (done) => {
  await timeout(1000);
  console.log('task2 done');
};

const task3 = async (done) => {
  await timeout(1000);
  console.log('task3 done');
};

exports.seriesTask = series(task1, task2, task3);

运行结果:

gulp-sample> yarn gulp seriesTask
yarn run v1.22.10
$ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp seriesTask
[10:14:36] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
[10:14:36] Starting 'seriesTask'...
[10:14:36] Starting 'task1'...
task1 done
[10:14:38] Finished 'task1' after 2.02 s
[10:14:38] Starting 'task2'...
task2 done
[10:14:39] Finished 'task2' after 1.01 s
[10:14:39] Starting 'task3'...
task3 done
[10:14:40] Finished 'task3' after 1.01 s
[10:14:40] Finished 'seriesTask' after 4.05 s
Done in 5.20s.

可以看出,通过使用 async/await 的语法糖后,代码变得更加的简洁,可读性也高了。

gulp 构建过程核心

其实对于文件的处理,逻辑上都是共通的,步骤总归是:

  1. 将文件手动复制粘帖到压缩工具中
  2. 获取压缩过后的结果
  3. 将文件粘贴回需要上线的文件夹中

使用 gulp 的原理也是这样,只不过原本由人工操作的部分转嫁给了机器(代码),速度更快,出错率也更低。

这里会通过使用 node 原生的 API 去模拟一下 gulp 内部的实现过程。

  1. 创建文件的读取也写入流

    这一步就是文件的 IO,不是很难:

    const fs = require('fs');
    
    exports.default = () => {
      // 文件读取流
      const read = fs.createReadStream('normalize.css');
      // 文件写入流
      const write = fs.createWriteStream('normalize.min.css');
    
      // 将读取出来的文件放入写入流
      read.pipe(write);
    
      return read;
    };
    

    这里导出的是 default,所以运行方式依然是 yarn gulp

    运行结果:

    gulp-sample> yarn gulp
     yarn run v1.22.10
     $ C:\\assignment\\front\\gulp\\gulp-sample\\node_modules\\.bin\\gulp
     [10:32:21] Using gulpfile C:\\assignment\\front\\gulp\\gulp-sample\\gulpfile.js
     [10:32:21] Starting 'default'...
     [10:32:21] Finished 'default' after 9.68 ms
     Done in 1.53s.
    

    能够看到一个与 normalize.css 内容一样的文件被导出。

    这里使用的 normalize.css 用的是 CSS 初始化的两种常见方法 中的内容,出于测试目的,任何的文件都可以使用。

  2. 压缩文件

    这一步会通过使用 stream 的 API: Transform 进行实现。

    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
          const input = chunk.toString();
          const output = input
            // 去除所有空格
            .replace(/\\s+/g, '')
            // 去除所有备注
            .replace(/\\/\\*.+?\\*\\//g, '');
          callback(null, output);
        },
      });
    
      // 将读取出来的文件,先经过压缩,再放入写入流
      read.pipe(transform).pipe(write);
    
      return read;
    };
    

    运行命令依旧一样,运行结果就不一样了:

    这时候 normalize.min.css 已经被压缩过了,而没有保留原本的格式。

可以看出来,gulp 的使用核心就是通过文件操作的方式去进行的。

gulp 文件操作 api

gulp 主要

以上是关于逐步详解如何使用 gulp 去进行自动化构建的主要内容,如果未能解决你的问题,请参考以下文章

详解常用的gulp命令

Gulp

自动化构建工具Gulp基础使用指南

Gulp

gulp自动化构建工具使用总结

自动化构建工具gulp简单介绍及使用