关于 Gulp 的学习与使用
关于 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模块
- For TypeScript, rename to
gulpfile.tsand install the ts-node (opens in a new tab) module. - For Babel, rename to
gulpfile.babel.jsand install the @babel/register (opens in a new tab) module.
大多数新版本的节点支持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)
任务可以被视为公共任务或私人任务。
-
公共任务 Public tasks 从您的gulpfile导出,这允许它们由gulp命令运行。
-
私有任务 Private tasks 用于内部,通常用作系列 series() 或并行 parallel() 组合的一部分。
私人任务的外观和行为与任何其他任务相似,但最终用户永远无法独立执行。
要公开注册任务,请将其从您的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/"));
};
