关于 Gulp 的学习与使用

You,13 min read

关于 Gulp 的学习与使用

gulp document (opens in a new tab)

快速入门 (Quick Start)

全局安装 gulp 命令

sudo npm install gulp -g
 
sudo yarn add gulp -g

项目中安装 gulp-cli 工具

yarn add gulp-cli -S
 
npm install gulp-cli -S

项目中创建 gulpfile.js 入口文件

Using your text editor, create a file named gulpfile.js in your project root with these contents

// gulpfile.js 
function defaultTask(cb) {
 
// place code for your default task here
 
cb();
 
}
 
exports.default = defaultTask

启用 gulp 命令

 
  gulp git:(gulp)  touch gulpfile.js
  gulp git:(gulp)  gulp
[12:55:57] Using gulpfile ~/Work/GitHub/notes/engine/gulp/gulpfile.js
[12:55:57] Starting 'default'...
[12:55:57] Finished 'default' after 1.98 ms
 

JavaScript and Gulpfile

Gulpfile 详解(Gulpfile explained)

gulpfile是项目目录中名为gulpfile.js(或大写为Gulpfile.js,如Makefile)的文件,当您运行gulp命令时会自动加载。

在此文件中,您经常会看到gulp API,如src()、dest()、se系列()或parall(),但可以使用任何香草JavaScript或节点模块。

任何导出的功能都将注册到gulp的任务系统中

转译( Transpilation)

您可以使用需要编译的语言(如TypeScript或Babel)编写gulpfile,方法是更改gulpfile.js上的扩展名来指示语言并安装匹配的transpiler模块

大多数新版本的节点支持TypeScript或Babel提供的大多数功能,但导入/导出语法除外。

当只需要该语法时,重命名为gulpfile.esm.js并安装esm模块

有关此主题的更高级深入了解和支持的扩展的完整列表,请参阅我们的gulpfile编译文档 (opens in a new tab)

拆分 gulpfile(Splitting a gulpfile)

许多用户首先将所有逻辑添加到gulpfile中。如果它长得太大,可以重构成单独的文件。

每个任务都可以拆分到自己的文件中,然后导入到您的gulp文件中进行组合。这不仅使事情井然有序,还允许您独立测试每项任务或根据条件更改组成。

Node的模块分辨率允许您将gulpfile.js文件替换为名为gulpfile.js的目录,该目录包含被视为gulpfile.js的index.js文件。

然后,此目录可以包含您用于任务的单个模块。

如果您正在使用编译器,请相应地命名文件夹和文件

创建任务(Creating Tasks)

每个gulp任务都是一个异步JavaScript函数-一个接受错误优先回调或返回流、promise、事件发射器、子进程或可观察的函数(稍后会详细介绍)。由于一些平台限制,不支持同步任务,尽管有一个非常巧妙的替代方案。

导出任务(Exporting)

任务可以被视为公共任务或私人任务。

私人任务的外观和行为与任何其他任务相似,但最终用户永远无法独立执行。

要公开注册任务,请将其从您的gulpfile导出。

const { series } = require('gulp');
 
// The `clean` function is not exported so it can be considered a private task.
// It can still be used within the `series()` composition.
function clean(cb) {
  // body omitted
  cb();
}
 
// The `build` function is exported so it is public and can be run with the `gulp` command.
// It can also be used within the `series()` composition.
function build(cb) {
  // body omitted
  cb();
}
 
exports.build = build;
exports.default = series(clean, build);
 
 
  gulp git:(gulp)  gulp --tasks
[13:22:28] Tasks for ~/Work/GitHub/notes/engine/gulp/gulpfile.js
[13:22:28] ├── build
[13:22:28] └─┬ default
[13:22:28]   └─┬ <series>
[13:22:28]     ├── clean
[13:22:28]     └── build
 

过去,'task()'用于将您的函数注册为任务。虽然该API仍然可用,但导出应该是主要注册机制,除非出口不起作用的边缘情况

组合任务(Compose tasks)

Gulp提供了两种强大的组合方法,系列series()和并行 parallel(),允许将单个任务组合成更大的操作。

这两种方法都接受任意数量的任务函数或组合操作。

series() 和 parallel() 可以嵌套在自己内部或彼此的任何深度。

要让您的任务按顺序执行,请使用 series() 方法。

 
const { series } = require('gulp');
 
function transpile(cb) {
  // body omitted
  cb();
}
 
function bundle(cb) {
  // body omitted
  cb();
}
 
exports.build = series(transpile, bundle);
 

要使任务以最大并发运行,请将它们与parall()方法相结合

const { parallel } = require('gulp');
 
function javascript(cb) {
  // body omitted
  cb();
}
 
function css(cb) {
  // body omitted
  cb();
}
 
exports.build = parallel(javascript, css);

注意:Task never defined: default exports.default 是必要的

 
  gulp git:(gulp)  gulp -f gulpfile.js
[13:30:08] Using gulpfile ~/Work/GitHub/notes/engine/gulp/gulpfile.js
[13:30:08] Task never defined: default
[13:30:08] To list available tasks, try running: gulp --tasks
 

当调用 series() 或 parallel() 时,任务会立即组成。这允许在单个任务中改变组合,而不是条件行为

const { series } = require('gulp');
 
function minify(cb) {
  // body omitted
  cb();
}
 
 
function transpile(cb) {
  // body omitted
  cb();
}
 
function livereload(cb) {
  // body omitted
  cb();
}
 
// 根据不同条件 组合不同的任务
 
 
if (process.env.NODE_ENV === 'production') {
  exports.build = series(transpile, minify);
} else {
  exports.build = series(transpile, livereload);
}

Series() 和 parallel() 可以嵌套到任意深度

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
);

当运行组合操作时,每次引用时都会执行每个任务。

例如,在两个不同任务之前引用的干净任务将运行两次,并导致不希望的结果。相反,重构最终组合中指定的干净任务。

如果您有这样的代码:

// This is INCORRECT
const { series, parallel } = require('gulp');
 
const clean = function(cb) {
  // body omitted
  cb();
};
 
const css = series(clean, function(cb) {
  // body omitted
  cb();
});
 
const javascript = series(clean, function(cb) {
  // body omitted
  cb();
});
 
exports.build = parallel(css, javascript);

迁移到这个

 
const { series, parallel } = require('gulp');
 
function clean(cb) {
  // body omitted
  cb();
}
 
function css(cb) {
  // body omitted
  cb();
}
 
function javascript(cb) {
  // body omitted
  cb();
}
 
exports.build = series(clean, parallel(css, javascript));
 

异步执行( Async Completion)

Node 库以多种方式处理异步功能。最常见的模式是 error-first callbacks (opens in a new tab),但是你还可能会遇到 streams (opens in a new tab)promises (opens in a new tab)event emitters (opens in a new tab)child processes (opens in a new tab), 或 observables (opens in a new tab)。gulp 任务(task)规范化了所有这些类型的异步功能

任务完成通知

当从任务(task)中返回 stream、promise、event emitter、child process 或 observable 时,成功或错误值将通知 gulp 是否继续执行或结束。如果任务(task)出错,gulp 将立即结束执行并显示该错误。

当使用 series() 组合多个任务(task)时,任何一个任务(task)的错误将导致整个任务组合结束,并且不会进一步执行其他任务。当使用 parallel() 组合多个任务(task)时,一个任务的错误将结束整个任务组合的结束,但是其他并行的任务(task)可能会执行完,也可能没有执行完

返回 stream

// 返回 stream
const { src, dest } = require('gulp'); 
 
function streamTask() { 
 return src('*.js') .pipe(dest('output')); 
} 
 
exports.default = streamTask;
 
// 返回 promise
function promiseTask() { 
 return Promise.resolve('the value is ignored'); 
} 
 
exports.default = promiseTask;
 
// 返回 event emitter
const { EventEmitter } = require('events');
 
function eventEmitterTask() { 
 // Emit has to happen async otherwise gulp isn't listening yet 
 const emitter = new EventEmitter(); 
 setTimeout(() => emitter.emit('finish'), 250); 
 return emitter; 
} 
 
exports.default = eventEmitterTask;
 
// 返回 child process
const { exec } = require('child_process'); 
 
function childProcessTask() { return exec('date'); } 
 
exports.default = childProcessTask;
 
// 返回 observable
const { Observable } = require("rxjs");
 
function observableTask() {
  return Observable.of(1, 2, 3);
}
 
exports.default = observableTask;
 
 

使用 callback

如果任务(task)不返回任何内容,则必须使用 callback 来指示任务已完成。在如下示例中,callback 将作为唯一一个名为 cb() 的参数传递给你的任务(task)。

function callbackTask(cb) {
  // `cb()` should be called by some async work
  cb();
}
 
exports.default = callbackTask;
 

如需通过 callback 把任务(task)中的错误告知 gulp,请将 Error 作为 callback 的唯一参数。

function callbackError(cb) {
  // `cb()` should be called by some async work
  cb(new Error("kaboom"));
}
 
exports.default = callbackError;
 
 

gulp 不再支持同步任务

gulp 不再支持同步任务(Synchronous tasks)了。因为同步任务常常会导致难以调试的细微错误,例如忘记从任务(task)中返回 stream。

当你看到 "Did you forget to signal async completion?" 警告时,说明你并未使用前面提到的返回方式。你需要使用 callback 或返回 stream、promise、event emitter、child process、observable 来解决此问题

使用 async/await

如果不使用前面提供到几种方式,你还可以将任务(task)定义为一个 async 函数 (opens in a new tab),它将利用 promise 对你的任务(task)进行包装。这将允许你使用 await 处理 promise,并使用其他同步代码

 
const fs = require("fs");
 
async function asyncAwaitTask() {
  const { version } = fs.readFileSync("package.json");
  console.log(version);
  await Promise.resolve("some result");
}
 
exports.default = asyncAwaitTask;
 
 
 
 

处理文件

gulp 暴露了 src() 和 dest() 方法用于处理计算机上存放的文件。

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

由 src() 产生的流(stream)应当从任务(task)中返回并发出异步完成的信号,就如 创建任务(task) (opens in a new tab) 文档中所述。

const { src, dest } = require("gulp");
 
exports.default = function () {
  return src("src/*.js").pipe(dest("output/"));
};
 
 

流(stream)所提供的主要的 API 是 .pipe() 方法,用于连接转换流(Transform streams)或可写流(Writable streams)

 
const { src, dest } = require("gulp");
const babel = require("gulp-babel");
 
exports.default = function () {
  return src("src/*.js").pipe(babel()).pipe(dest("output/"));
};
 
 

dest() 接受一个输出目录作为参数,并且它还会产生一个 Node 流(stream) (opens in a new tab),通常作为终止流(terminator stream)。当它接收到通过管道(pipeline)传输的文件时,它会将文件内容及文件属性写入到指定的目录中。gulp 还提供了 symlink()方法,其操作方式类似 dest(),但是创建的是链接而不是文件( 详情请参阅 symlink() (opens in a new tab) )。

大多数情况下,利用 .pipe() 方法将插件放置在 src() 和 dest() 之间,并转换流(stream)中的文件

向流(stream)中添加文件

src() 也可以放在管道(pipeline)的中间,以根据给定的 glob 向流(stream)中添加文件。新加入的文件只对后续的转换可用。如果 glob 匹配的文件与之前的有重复 (opens in a new tab),仍然会再次添加文件。

这对于在添加普通的 JavaScript 文件之前先转换部分文件的场景很有用,添加新的文件后可以对所有文件统一进行压缩并混淆(uglifying)。

const { src, dest } = require("gulp");
const babel = require("gulp-babel");
const uglify = require("gulp-uglify");
 
exports.default = function () {
  return src("src/*.js")
    .pipe(babel())
    .pipe(src("vendor/*.js"))
    .pipe(uglify())
    .pipe(dest("output/"));
};
 
 

2026 © Lizhenyui.