不懂不要乱讲谁说Gulp淘汰了

1,208 阅读57分钟

前言

作为老牌前端构建工具使用,无论是生态还是灵活性都不如新生力量webpack/vite 但是不意味着他就没有任何作用。作为简单任务自动化工具来使用还是非常不错的。(这里就是我需要流程化执行node脚本而学习并使用gulp)

简单任务自动化优势

  • 轻量级项目首选:对于小型项目或者简单的静态网页开发,并不需要复杂的模块打包功能。Gulp 可以快速地完成文件的复制、压缩、编译等基本任务。例如一个简单的单页静态网站,只需要将 HTML、CSS、JavaScript 文件进行简单处理并部署到服务器,使用 Gulp 可以轻松实现。编写几个简单的任务,就能够自动化完成文件的处理流程,无需像 Webpack 那样进行复杂的配置。
  • 独立任务处理高效:当项目中存在一些独立的、与模块打包无关的任务时,Gulp 是非常合适的选择。比如定期清理项目中的临时文件、备份特定文件等。这些任务可以独立于项目的打包流程,使用 Gulp 可以将这些任务集成到构建脚本中,实现自动化处理。

与其他工具良好协作

  • 搭配 Webpack 或 Vite:Gulp 可以与 Webpack、Vite 等工具结合使用,发挥各自的优势。在一个大型项目中,可以使用 Webpack 或 Vite 进行模块打包和优化,同时使用 Gulp 来处理一些额外的任务,如图片压缩、字体文件处理等。这样既利用了 Webpack、Vite 的强大打包功能,又借助了 Gulp 在文件处理方面的灵活性。
  • 整合第三方工具:Gulp 能够方便地整合各种第三方工具和命令行工具。例如,可以使用 Gulp 调用命令行工具来执行代码检查(如 ESLint、Stylelint)、单元测试(如 Jest、Mocha)等任务。通过 Gulp 的任务编排,可以将这些工具集成到项目的构建流程中,实现一站式的开发体验。

生态系统稳定且实用

  • 丰富的插件库:虽然 Gulp 的生态系统在活跃度上可能不如 Webpack 和 Vite,但它拥有大量成熟且稳定的插件。这些插件经过了长时间的发展和测试,能够满足各种常见的构建需求。例如 gulp - sass 用于编译 Sass 文件,gulp - uglify 用于压缩 JavaScript 文件,gulp - imagemin 用于优化图片等。这些插件的稳定性为项目的构建提供了可靠的保障。
  • 社区支持与文档完善:Gulp 作为一款老牌的前端构建工具,拥有庞大的用户社区和丰富的文档资源。当开发者在使用过程中遇到问题时,可以很容易地在社区中找到解决方案。同时,详细的文档也有助于新手快速上手和深入学习 Gulp 的使用方法。

学习成本低与兼容性强

  • 易于上手:Gulp 的 API 简洁明了,对于初学者来说非常容易理解和掌握。只需要了解基本的文件流操作和任务定义方式,就可以开始编写构建脚本。这使得团队中的新成员能够快速融入项目的构建流程,降低了团队的技术门槛。
  • 广泛的兼容性:Gulp 基于 Node.js 开发,具有良好的跨平台兼容性。无论是在 Windows、Mac 还是 Linux 系统上,都可以稳定运行。同时,它对各种前端技术和框架都有很好的支持,不会受到特定框架或技术栈的限制。

介绍

Gulp 是一款基于 Node.js 的自动化构建工具,在前端开发中应用广泛,以下是关于它的详细介绍:

特点

  • 任务驱动:Gulp 基于任务流的概念,允许开发者将复杂的构建过程拆分成多个简单的任务,每个任务负责完成一项具体的工作,如文件压缩、代码编译、文件合并等。通过定义不同的任务,并指定它们之间的依赖关系,Gulp 可以按照预定的顺序自动执行这些任务,实现整个项目的自动化构建。
  • 流操作:Gulp 利用 Node.js 的流(Stream)概念来处理文件。它可以将文件以流的形式读取进来,在流的管道中对文件进行各种操作,如转换、过滤、合并等,最后再将处理后的文件以流的形式输出到指定位置。这种基于流的操作方式使得 Gulp 在处理大文件时非常高效,能够减少内存占用,提高构建速度。
  • 插件丰富:Gulp 拥有大量的插件,这些插件可以满足各种不同的构建需求。例如,通过gulp-sass插件可以将 Sass 文件编译成 CSS 文件,使用gulp-uglify插件可以压缩 JavaScript 代码,gulp-imagemin插件用于优化图片等。开发者可以根据项目的具体需求选择合适的插件,轻松扩展 Gulp 的功能。
  • 易于使用:Gulp 的 API 非常简洁,易于学习和使用。开发者只需要在项目中安装 Gulp 及相关插件,然后在gulpfile.js文件中定义任务和配置选项,就可以快速搭建起自动化构建流程。而且,Gulp 的命令行工具也很简单,通过在命令行中输入相应的命令就可以执行定义好的任务。

工作原理

  • 读取文件:Gulp 首先通过文件系统(FS)模块读取项目中的源文件,这些文件可以是 HTML、CSS、JavaScript、图片等各种类型的资源。
  • 转换操作:读取进来的文件会以流的形式进入 Gulp 的操作管道,在管道中可以通过各种插件对文件进行转换操作。这些操作可以是编译、压缩、合并、语法检查等。
  • 输出文件:经过一系列的转换操作后,文件最终会以流的形式输出到指定的目标位置,通常是项目的发布目录或其他指定的文件夹。在输出过程中,Gulp 会根据配置选项来确定输出文件的名称、路径和格式等。

应用场景

  • 代码编译:可以将 TypeScript、CoffeeScript 等编程语言编写的代码编译成浏览器能够直接识别的 JavaScript 代码,也能将 Sass、Less 等预处理器编写的样式代码编译成 CSS。
  • 文件压缩:在项目发布前,Gulp 可以对 JavaScript、CSS、图片等文件进行压缩,减小文件体积,提高网站的加载速度。例如,压缩 JavaScript 文件可以去除不必要的空格、注释等,压缩图片可以降低图片的分辨率、优化图片格式等。
  • 代码检查:利用 Gulp 可以在开发过程中对代码进行语法检查和代码规范检查,如使用 ESLint 对 JavaScript 代码进行语法和风格检查,确保代码的质量和一致性,帮助开发者及时发现和纠正代码中的错误和不规范之处。
  • 浏览器自动刷新:在开发过程中,Gulp 可以实现浏览器自动刷新功能,当代码发生变化时,自动刷新浏览器,实时显示代码的修改效果,提高开发效率。

快速上手

npm install --global gulp-cli
npm install --save-dev gulp

创建 gulpfile 文件

利用任何文本编辑器在项目大的根目录下创建一个名为 gulpfile.js 的文件,并在文件中输入以下内容:

function defaultTask(cb) {
  // place code for your default task here
  cb();
}

exports.default = defaultTask

测试

在项目根目录下执行 gulp 命令:

gulp

如需运行多个任务(task),可以执行 gulp <task> <othertask>

输出结果

默认任务(task)将执行,因为任务为空,因此没有实际动作。

核心概念

Gulpfile.js

可以理解为入口文件当执行gulp命令时会被自动加载

Node 的模块的解析功能允许你将 gulpfile.js' 文件替换为同样命名为 gulpfile.js 的文件夹,该文件夹中包含了一个名为 index.js 的文件,该 index.js 文件将被当作 gulpfile.js 使用。

task

task(任务)是一个异步的 JavaScript 函数,此函数是一个可以接收 callback 作为参数的函数,或者是一个返回 stream、promise、event emitter、child process 或 observable 类型值的函数。

任务类型

任务(tasks)可以是 public(公开)  或 private(私有)  类型的。

  • 公开任务(Public tasks)  从 gulpfile 中被导出(export),可以通过 gulp 命令直接调用。
  • 私有任务(Private tasks)  被设计为在内部使用,通常作为 series() 或 parallel() 组合的组成部分。

组合任务

Gulp 提供了两个强大的组合方法: series() 和 parallel(),允许将多个独立的任务组合为一个更大的操作。这两个方法都可以接受任意数目的任务(task)函数或已经组合的操作。series() 和 parallel() 可以互相嵌套至任意深度。

如果需要让任务(task)按顺序执行,请使用 series() 方法。

// series 顺序执行函数
const { series } = require('gulp');  
  
function transpile(cb) {  
// body omitted  
cb();  
}  
  
function bundle(cb) {  
// body omitted  
cb();  
}  

exports.build = series(transpile, bundle);

对于希望以最大并发来运行的任务(tasks),可以使用 parallel() 方法将它们组合起来。

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

任务(task)完成通知

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

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

返回 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() {
  const emitter = new EventEmitter();
  // Emit has to happen async otherwise gulp isn't listening yet
  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;

然而,你通常会将此 callback 函数传递给另一个 API ,而不是自己调用它。

const fs = require('fs');

function passingCallback(cb) {
  fs.access('gulpfile.js', cb);
}

exports.default = passingCallback;

使用 async/await解决异步

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

const fs = require('fs');

async function asyncAwaitTask() {
  const { version } = JSON.parse(fs.readFileSync('package.json', 'utf8'));
  console.log(version);
  await Promise.resolve('some result');
}

exports.default = asyncAwaitTask;

处理文件

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

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

由 src() 产生的流(stream)应当从任务(task)中返回并发出异步完成的信号。

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),通常作为终止流(terminator stream)。当它接收到通过管道(pipeline)传输的文件时,它会将文件内容及文件属性写入到指定的目录中。gulp 还提供了 symlink() 方法,其操作方式类似 dest(),但是创建的是链接而不是文件( 详情请参阅 symlink() )。

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

向流(stream)中添加文件

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

这对于在添加普通的 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/'));
}

分阶段输出

dest() 可以用在管道(pipeline)中间用于将文件的中间状态写入文件系统。当接收到一个文件时,当前状态的文件将被写入文件系统,文件路径也将被修改以反映输出文件的新位置,然后该文件继续沿着管道(pipeline)传输。

此功能可用于在同一个管道(pipeline)中创建未压缩(unminified)和已压缩(minified)的文件。

const { src, dest } = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');

exports.default = function() {
  return src('src/*.js')
    .pipe(babel())
    .pipe(src('vendor/*.js'))
    .pipe(dest('output/'))
    .pipe(uglify())
    .pipe(rename({ extname: '.min.js' }))
    .pipe(dest('output/'));
}

模式:流动(streaming)、缓冲(buffered)和空(empty)模式

src() 可以工作在三种模式下:缓冲(buffering)、流动(streaming)和空(empty)模式。这些模式可以通过对 src() 的 buffer 和 read 参数 进行设置。

  • 缓冲(Buffering)模式是默认模式,将文件内容加载内存中。插件通常运行在缓冲(buffering)模式下,并且许多插件不支持流动(streaming)模式。
  • 流动(Streaming)模式的存在主要用于操作无法放入内存中的大文件,例如巨幅图像或电影。文件内容从文件系统中以小块的方式流式传输,而不是一次性全部加载。如果需要流动(streaming)模式,请查找支持此模式的插件或自己编写。
  • 空(Empty)模式不包含任何内容,仅在处理文件元数据时有用。

glob字符串

glob 是由普通字符和/或通配字符组成的字符串,用于匹配文件路径。可以利用一个或多个 glob 在文件系统中定位文件。


字符串片段(segment)是指两个分隔符之间的所有字符组成的字符串。在 glob 中,分隔符永远是 / 字符 - 不区分操作系统 - 即便是在采用 \ 作为分隔符的 Windows 操作系统中。在 glob 中,\ 字符被保留作为转义符使用。

如下, * 被转义了,因此,* 将被作为一个普通字符使用,而不再是通配符了。

避免使用 Node 的 path 类方法来创建 glob,例如 path.join。在 Windows 中,由于 Node 使用 \ 作为路径分隔符,因此将会产生一个无效的 glob。还要避免使用 __dirname 和 __filename 全局变量,由于同样的原因,process.cwd() 方法也要避免使用。

特殊字符: * (一个星号)

在一个字符串片段中匹配任意数量的字符,包括零个匹配。对于匹配单级目录下的文件很有用。

下面这个 glob 能够匹配类似 index.js 的文件,但是不能匹配类似 scripts/index.js 或 scripts/nested/index.js 的文件。

'*.js'

特殊字符: ** (两个星号)

在多个字符串片段中匹配任意数量的字符,包括零个匹配。 对于匹配嵌套目录下的文件很有用。请确保适当地限制带有两个星号的 glob 的使用,以避免匹配大量不必要的目录。

下面这个 glob 被适当地限制在 scripts/ 目录下。它将匹配类似 scripts/index.jsscripts/nested/index.js 和 scripts/nested/twice/index.js 的文件。

'scripts/**/*.js'

在上面的示例中,如果没有 scripts/ 这个前缀做限制,node_modules 目录下的所有目录或其他目录也都将被匹配。

特殊字符: ! (取反)

以 ! 字符开头的通配符会对该通配符进行 “取反” 操作,即完全排除匹配到的内容。所有取反的通配符都会应用到每个正向通配符上,这与 Gulp 5 之前的版本有所不同。

在这里,会遍历 scripts/ 目录以查找所有以 .js 结尾的文件,但 scripts/vendor/ 目录下的所有文件都会被排除在外。

['scripts/**/*.js', '!scripts/vendor/**']

取反通配符可以作为一种限制双星号通配符匹配范围的替代方法。

['**/*.js', '!node_modules/**']

有序通配符

Gulp 5 之前的版本支持 “有序通配符”;不过,为了与生态系统中的大多数通配符匹配库保持一致,该功能已被移除。

const order = require("ordered-read-streams");
  exports.default = function () {
    return order([
      gulp.src("input/jquery/dist/jquery.js"),
      gulp.src("input/detect_swipe/jquery.detect_swipe.js"),
    ]).pipe(gulp.dest('output/'));
  }

如果你需要 “有序通配符” 功能,可以使用 ordered - read - streams 库来合并流:

匹配重叠(Overlapping globs)

两个或多个 glob 故意或无意匹配了相同的文件就被认为是匹配重叠(overlapping)了。如果在同一个 src() 中使用了会产生匹配重叠的 glob,gulp 将尽力去除重叠部分,但是在多个 src() 调用时产生的匹配重叠是不会被去重的。

使用插件

Gulp 插件实质上是 Node 转换流(Transform Streams),它封装了通过管道(pipeline)转换文件的常见功能,通常是使用 .pipe() 方法并放在 src() 和 dest() 之间。他们可以更改经过流(stream)的每个文件的文件名、元数据或文件内容。

托管在 npm 上的插件 - 标记有 "gulpplugin" 和 "gulpfriendly" 关键词 - 可以在 插件搜索页面 上浏览和搜索。

每个插件应当只完成必要的工作,因此你可以把它们像构建块一样连接在一起。获得想要的结果可能需要把一组插件组合在一起使用。

const { src, dest } = require('gulp');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');

exports.default = function() {
  return src('src/*.js')
    // gulp-uglify 插件并不改变文件名
    .pipe(uglify())
    // 因此使用 gulp-rename 插件修改文件的扩展名
    .pipe(rename({ extname: '.min.js' }))
    .pipe(dest('output/'));
}

是否需要插件?

并非 gulp 中的一切都需要用插件来完成。虽然它们是一种快速上手的方法,但许多操作都应当通过使用独立的功能模块或库来实现。

const { rollup } = require('rollup');

// Rollup 提供了基于 promise 的 API,在 `async` 任务(task)中工作的很好
exports.default = async function() {
  const bundle = await rollup({
    input: 'src/index.js'
  });

  return bundle.write({
    file: 'output/bundle.js',
    format: 'iife'
  });
}

插件应当总是用来转换文件的。其他操作都应该使用(非插件的) Node 模块或库来实现。

const del = require('delete');

exports.default = function(cb) {
  // 直接使用 `delete` 模块,避免使用 gulp-rimraf 插件
  del(['output/*.js'], cb);
}

条件插件

因为插件的操作不应该针对特定文件类型,因此你可能需要使用像 gulp-if 之类的插件来完成转换某些文件的操作。

const { src, dest } = require('gulp');
const gulpif = require('gulp-if');
const uglify = require('gulp-uglify');

function isJavaScript(file) {
  // 判断文件的扩展名是否是 '.js'
  return file.extname === '.js';
}

exports.default = function() {
  // 在同一个管道(pipeline)上处理 JavaScript 和 CSS 文件
  return src(['src/*.js', 'src/*.css'])
    // 只对 JavaScript 文件应用 gulp-uglify 插件
    .pipe(gulpif(isJavaScript, uglify()))
    .pipe(dest('output/'));
}

内联插件(Inline plugins)

内联插件是一次性的转换流(Transform Streams),你可以通过在 gulpfile 文件直接书写需要的功能。

在两种情况下,创建内联插件很有用:

  • 避免自己创建并维护插件。
  • 避免 fork 一个已经存在的插件并添加自己所需的功能。
const { src, dest } = require('gulp');
const uglify = require('uglify-js');
const through2 = require('through2');

exports.default = function() {
  return src('src/*.js')
    // 创建一个内联插件,从而避免使用 gulp-uglify 插件
    .pipe(through2.obj(function(file, _, cb) {
      if (file.isBuffer()) {
        const code = uglify.minify(file.contents.toString())
        file.contents = Buffer.from(code.code)
      }
      cb(null, file);
    }))
    .pipe(dest('output/'));
}

文件监控

gulp api 中的 watch() 方法利用文件系统的监控程序(file system watcher)将 globs 与 任务(task) 进行关联。它对匹配 glob 的文件进行监控,如果有文件被修改了就执行关联的任务(task)。如果被执行的任务(task)没有触发 异步完成 信号,它将永远不会再次运行了。

此 API 的默认设置是基于通常的使用场景的,而且提供了内置的延迟和排队机制。

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

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

function javascript(cb) {
  // body omitted
  cb();
}

function css(cb) {
  // body omitted
  cb();
}

exports.default = function() {
  // 你可以使用单个任务
  watch('src/*.css', css);
  // 也可以组合多个任务
  watch('src/*.js', series(clean, javascript));
};

警告:避免同步任务

就像注册到任务系统中的任务(task)一样,与文件监控程序关联的任务(task)不能是同步任务(synchronous taks)。如果你将同步任务(task)关联到监控程序,则无法确定任务(task)的完成情况,任务(task)将不会再次运行(假定当前正在运行)。

由于文件监控程序会让你的 Node 进程保持持续运行,因此不会有错误或警告产生。由于进程没有退出,因此无法确定任务(task)是否已经完成还是运行了很久很久了。

可监控的事件

默认情况下,只要创建、更改或删除文件,文件监控程序就会执行关联的任务(task)。 如果你需要使用不同的事件,你可以在调用 watch() 方法时通过 events 参数进行指定。可用的事件有 'add''addDir''change''unlink''unlinkDir''ready''error'。此外,还有一个 'all' 事件,它表示除 'ready' 和 'error' 之外的所有事件。

const { watch } = require('gulp');

exports.default = function() {
  // 所有事件都将被监控
  watch('src/*.js', { events: 'all' }, function(cb) {
    // body omitted
    cb();
  });
};

初次执行

调用 watch() 之后,关联的任务(task)是不会被立即执行的,而是要等到第一次文件修之后才执行。

如需在第一次文件修改之前执行,也就是调用 watch() 之后立即执行,请将 ignoreInitial 参数设置为 false

const { watch } = require('gulp');

exports.default = function() {
  // 任务将在启动时执行
  watch('src/*.js', { ignoreInitial: false }, function(cb) {
    // body omitted
    cb();
  });
};

队列

watch() 方法能够保证当前执行的任务(task)不会再次并发执行。当文件监控程序关联的任务(task)正在运行时又有文件被修改了,那么所关联任务的这次新的执行将被放到执行队列中等待,直到上一次关联任务执行完之后才能运行。每一次文件修改只产生一次关联任务的执行并放入队列中。

如需禁止队列,请将 queue 参数设置为 false

const { watch } = require('gulp');

exports.default = function() {
  // 每次文件修改之后关联任务都将(并发)执行
  watch('src/*.js', { queue: false }, function(cb) {
    // body omitted
    cb();
  });
};

延迟

文件更改之后,只有经过 200 毫秒的延迟之后,文件监控程序所关联的任务(task)才会被执行。这是为了避免在同使更改许多文件时(例如查找和替换操作)过早启动任务(taks)的执行。

如需调整延迟时间,请为 delay 参数设置一个正整数。

const { watch } = require('gulp');

exports.default = function() {
  // 文件第一次修改之后要等待 500 毫秒才执行关联的任务
  watch('src/*.js', { delay: 500 }, function(cb) {
    // body omitted
    cb();
  });
};

使用监控程序实例

你可能不会使用到此功能,但是如果你需要对被修改的文件进行完全的掌控 (例如访问文件路径或元数据)请使用从 watch() 返回的 chokidar 实例。

注意:  返回的 chokidar 实例没有队列、延迟和异步完成(async completion)这些功能。

可选的依赖项

Gulp 有一个名为 fsevents 的可选依赖项,他是一个特定于 Mac 系统的文件监控程序。如果你看到安装 fsevents 时出现的警告信息 -  "npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents"  - 这并不是什么问题,忽略即可。 如果跳过 fsevents 的安装,将使用一个备用文件监控程序,后续在 gulpfile 中产生的任何错误都将与此警告无关。

Apis

src()

创建一个流,用于从文件系统读取 Vinyl 对象。

**注:**BOMs(字节顺序标记)在 UTF-8 中没有任何作用,除非使用 removeBOM 选项禁用,否则 src() 将从读取的 UTF-8 文件中删除BOMs。

用法

const { src, dest } = require('gulp');

function copy() {
  return src('input/*.js')
    .pipe(dest('output/'));
}

exports.copy = copy;

函数原型

src(globs, [options])
参数
参数类型描述
globsstring array用于监视文件系统中匹配文件的 Globs 。
optionsobject在下面的 选项 中详细说明。
返回值

返回一个可以在管道的开始或中间使用的流,用于根据给定的 globs 添加文件。

可能出现的错误

当 globs 参数只能匹配一个文件(如 foo/bar.js)而且没有找到匹配时,会抛出一个错误,提示 "File not found with singular glob"。若要抑制此错误,请将 allowEmpty 选项设置为 true

当在 globs 中给出一个无效的 glob 时,抛出一个错误,并显示 "Invalid glob argument"。

选项

对于接受函数的选项,传递的函数将与每个 Vinyl 对象一起调用,并且必须返回另一个列出类型的值。

名称类型默认值描述
encodingstring boolean"utf8"当为假时,文件内容将被视为二进制数据。当为字符串时,该字符串将用作文本编码。
bufferboolean functiontrue如果为 true,文件内容将被缓冲到内存中。如果为 false,Vinyl 对象的contents属性将是一个暂停的流。对于大文件,可能无法缓冲其内容。注意:插件可能不支持对流式内容的处理。
readboolean functiontrue如果为 false,文件将不会被读取,并且其对应的 Vinyl 对象也无法通过 .dest() 方法写入磁盘。.
sincedate timestamp function设置此值后,仅为自指定时间以来修改过的文件创建 Vinyl 对象。
removeBOMboolean functiontrue若为 true,则从 UTF - 8 编码的文件中移除字节顺序标记(BOM)。若为 false,则忽略字节顺序标记。
sourcemapsboolean functionfalse如果为 true,会在创建的 Vinyl 对象上启用源映射支持。加载内联源映射并解析外部源映射链接。
resolveSymlinksboolean functiontrue若为 true,则会递归地将符号链接解析为其目标文件。若为 false,则会保留符号链接,并将 Vinyl 对象的 symlink 属性设置为原始文件的路径。
cwdstringprocess.cwd()该目录将与任何相对路径相结合,以形成绝对路径。对于绝对路径,此设置会被忽略。使用此选项可避免使用 path.join() 来组合通配符模式。该选项会直接传递给 glob-stream
basestring显式设置所创建的 Vinyl 对象上的 base 属性。具体细节见 “API 概念” 部分。此选项会直接传递给 glob-stream
cwdbasebooleanfalse如果为 true,则 cwd(当前工作目录)和 base 选项应保持一致。此选项会直接传递给 glob-stream
rootstring通配符模式所基于的根路径。此选项会直接传递给 glob-stream
allowEmptybooleanfalse若为 false,对于那些只能匹配单个文件的通配符模式(例如 foo/bar.js),如果未找到匹配文件,将会抛出错误。若为 true,则会抑制通配符匹配失败的情况。此选项会直接传递给 glob-stream
uniqueBystring function'path'通过比较字符串属性名或函数的返回结果,从流中移除重复项。注意:使用函数时,该函数会接收流中的数据(包含 cwdbasepath 属性的对象)。
dotbooleanfalse如果为 true,则会将通配符模式与点文件(如 .gitignore)进行匹配。此选项会直接传递给 anymatch
nouniquebooleanfalse若为 false,则会防止结果集中出现重复文件。此选项会直接传递给 anymatch
debugbooleanfalse如果为 true,调试信息将被记录到命令行。此选项会直接传递给 anymatch
nobracebooleanfalse如果为 true,则避免展开花括号集合,例如 {a,b} 或 {1..3}。此选项会直接传递给 anymatch
noglobstarbooleanfalse如果为 true,则将双星号通配符字符(**)视为单星号通配符字符(*)。此选项会直接传递给 anymatch
noextbooleanfalse如果为 true,则避免匹配扩展通配符模式,例如 +(ab)。此选项会直接传递给 anymatch
nocasebooleanfalse如果为 true,则进行不区分大小写的匹配。注意:在不区分大小写的文件系统中,非通配符模式默认会进行不区分大小写的匹配。此选项会直接传递给 anymatch
matchBasebooleanfalse如果为 true 且通配符模式中不包含任何 / 字符,则会遍历所有目录并匹配该通配符模式。例如,*.js 会被视为等同于 **/*.js。此选项会直接传递给 anymatch
ignorestring array用于从匹配结果中排除的通配符模式。此选项会与取反后的 globs 相结合。注意:无论其他设置如何,这些通配符模式始终会针对点文件(以点开头的文件)进行匹配。此选项会直接传递给 anymatch

资源映射(Sourcemaps)

对资源映射(Sourcemap )的支持已内置到 src() 和 dest() 中了,但是默认情况下是禁用的。开启此功能能够生成内联或外联的资源映射(Sourcemap)。

内联资源映射(sourcemaps):

const { src, dest } = require('gulp');
const uglify = require('gulp-uglify');

src('input/**/*.js', { sourcemaps: true })
  .pipe(uglify())
  .pipe(dest('output/', { sourcemaps: true }));

外部资源映射:

const { src, dest } = require('gulp');
const uglify = require('gulp-uglify');

src('input/**/*.js', { sourcemaps: true })
  .pipe(uglify())
  .pipe(dest('output/', { sourcemaps: '.' }));

dest()

创建一个用于将 Vinyl 对象写入到文件系统的流。

用法

const { src, dest } = require('gulp');

function copy() {
  return src('input/*.js')
    .pipe(dest('output/'));
}

exports.copy = copy;

函数原型

dest(directory, [options])

参数

参数类型描述
directory (必须)string function文件将被写入的输出目录的路径。如果使用了函数,则每个 Vinyl 对象都会调用该函数,并且必须返回字符串形式的目录路径。
optionsobject详情见下文 选项

返回值

返回一个可以在管道的中间或末尾使用的流,用于在文件系统上创建文件。

每当 Vinyl 对象通过流被传递时,它将内容和其他细节写到给定目录下的文件系统。如果 Vinyl 对象具有 symlink 属性,将创建符号链接(symbolic link)而不是写入内容。创建文件后,将更新其元数据以匹配 Vinyl 对象。

在文件系统上创建文件时,Vinyl 对象将被修改。

  • cwdbase 和 path 属性将被更新以匹配创建的文件。
  • stat 属性将被更新,以匹配文件系统上的文件。
  • 如果 contents 属性是一个流,它将被重置,以便可以再次读取。

可能出现的错误

当目录为空字符串时,将抛出一个错误,并提示 "Invalid dest() folder argument. Please specify a non-empty string or a function."(无效的 dest() 文件夹参数。请指定非空字符串或函数。)

当目录不是字符串或函数时,将抛出一个错误,并提示 "Invalid dest() folder argument. Please specify a non-empty string or a function."

当 directory 是一个返回空字符串或 undefined 的函数时,将发出一条错误消息 “Invalid output folder”。

选项

对于接受函数的选项,传递的函数将与每个 Vinyl 对象一起调用,并且必须返回另一个列出类型的值。

名称类型默认值注解
cwdstring functionprocess.cwd()此目录可以与任何相对路径合并为绝对路径。如果是绝对路径,则忽略。该参数旨在避免通过 directory 和 path.join() 生成输出目录的路径的麻烦。
modenumber functionVinyl 对象的 stat.mode 属性创建文件时使用的模式。如果没有设置,并且缺少 stat.mode,则使用 process' 模式。
dirModenumber function创建目录时使用的模式。如果没有设置,将使用当前进程的模式。
overwriteboolean functiontrue如果为 true,则覆盖相同路径的现有文件。
appendboolean functionfalse如果为 true,则将内容添加到文件末尾,而不是替换现有内容。
sourcemapsboolean string functionfalse如果为 true,则将内联 sourcemaps 写入输出文件。 如果指定一个 string 类型的路径,则在给定的路径出写入外部 sourcemaps 。
relativeSymlinksboolean functionfalse如果为 false,则创建的任何符号链接都将是绝对路径的。 注意:如果正在创建的是 junction,则将被忽略,因为他们必须是绝对路径。
useJunctionsboolean functiontrue此选项仅用适用于 Windows,在其他操作系统中将被忽略。当设置为 true 时,则将目录符号链接创建为 junction。详情请参见下面的 Windows 上的符号链接 。

元数据更新

每当 dest() 流创建一个文件时,就会将 Vinyl 对象的 modemtime 和 atime 与创建的文件进行比较。如果它们不同,创建的文件将被更新以反映 Vinyl 对象的元数据。如果这些属性相同,或者 gulp 没有更改的权限,则会跳过该尝试。

在不支持 Node 的 process.getuid()或 process.geteuid() 方法的 Windows 或其他操作系统上禁用此功能。这是因为Windows通过使用 fs.fchmod() 和 `fs.futimes() 会产生意想不到的结果。

注意fs.futimes() 在内部将 mtime 和 atime 时间戳转换为秒。这种除以 1000 的方法可能会导致 32 位操作系统的精度有所下降。

Sourcemaps

Sourcemap 支持直接构建到 src() 和 dest() 中,但默认情况下是禁用的。使其能够生成内联或外部 sourcemaps。

内联 sourcemaps:

const { src, dest } = require('gulp');
const uglify = require('gulp-uglify');

src('input/**/*.js', { sourcemaps: true })
  .pipe(uglify())
  .pipe(dest('output/', { sourcemaps: true }));

外部 sourcemaps:

const { src, dest } = require('gulp');
const uglify = require('gulp-uglify');

src('input/**/*.js', { sourcemaps: true })
  .pipe(uglify())
  .pipe(dest('output/', { sourcemaps: '.' }));

Windows 上的符号链接

在 Windows 上创建符号链接时,type 参数被传递给 Node 的 fs.symlink() 方法,该方法指定被链接的目标的类型。链接类型设置为:

  • 'file',当目标是一个常规文件时
  • 'junction', 当目标是一个目录时
  • 'dir', 当目标是一个目录并且用户禁用了 useJunctions 选项时

如果试图创建 dangling (指向不存在的目标)链接,则无法自动确定链接类型。在这些情况下,根据 dangling 链接是通过 symlink() 创建的还是通过 dest() 创建的,行为会有所不同。

对于通过 symlink() 创建的 dangling 链接,传入的 Vinyl 对象表示目标,因此其 stats 将确定所需的链接类型。如果 isDirectory() 返回 false,则创建一个 'file' 链接,否则根据 useJunctions 选项的值创建一个 'junction' 或 'dir' 链接。

对于通过 dest() 创建的 dangling 链接,传入的 Vinyl 对象表示链接——通常通过 src(..., { resolveSymlinks: false }) 从磁盘加载。在这种情况下,无法合理地确定链接类型,默认使用 'file'。如果正在创建指向目录的 dangling 链接,这可能会导致意外行为。避免这种情况。

symlink()

创建一个流(stream),用于连接 Vinyl 对象到文件系统。

用法

const { src, symlink } = require('gulp');

function link() {
  return src('input/*.js')
    .pipe(symlink('output/'));
}

exports.link = link;

函数原型

symlink(directory, [options])

参数

参数类型描述
directory (required)string functionThe path of the output directory where symbolic links will be created. If a function is used, the function will be called with each Vinyl object and must return a string directory path.
optionsobjectDetailed in Options below.

返回值

A stream that can be used in the middle or at the end of a pipeline to create symbolic links on the file system. Whenever a Vinyl object is passed through the stream, it creates a symbolic link to the original file on the file system at the given directory.

Whenever a symbolic link is created on the file system, the Vinyl object will be modified.

  • The cwdbase, and path properties will be updated to match the created symbolic link.
  • The stat property will be updated to match the symbolic link on the file system.
  • The contents property will be set to null.
  • The symlink property will be added or replaced with original path.

Note:  On Windows, directory links are created using junctions by default. The useJunctions option disables this behavior.

可能出现的错误

When directory is an empty string, throws an error with the message, "Invalid symlink() folder argument. Please specify a non-empty string or a function."

When directory is not a string or function, throws an error with the message, "Invalid symlink() folder argument. Please specify a non-empty string or a function."

When directory is a function that returns an empty string or undefined, emits an error with the message, "Invalid output folder".

选项

对于接受函数的选项,传递的函数将与每个 Vinyl 对象一起调用,并且必须返回另一个列出类型的值。

nametypedefaultnote
cwdstring functionprocess.cwd()The directory that will be combined with any relative path to form an absolute path. Is ignored for absolute paths. Use to avoid combining directory with path.join().
dirModenumber functionThe mode used when creating directories. If not set, the process' mode will be used.
overwriteboolean functiontrueWhen true, overwrites existing files with the same path.
relativeSymlinksboolean functionfalseWhen false, any symbolic links created will be absolute. Note: Ignored if a junction is being created, as they must be absolute.
useJunctionsboolean functiontrueThis option is only relevant on Windows and ignored elsewhere. When true, creates directory symbolic link as a junction. Detailed in Symbolic links on Windows below.

Symbolic links on Windows

When creating symbolic links on Windows, a type argument is passed to Node's fs.symlink() method which specifies the type of target being linked. The link type is set to:

  • 'file' when the target is a regular file
  • 'junction' when the target is a directory
  • 'dir' when the target is a directory and the user disables the useJunctions option

If you try to create a dangling (pointing to a non-existent target) link, the link type can't be determined automatically. In these cases, behavior will vary depending on whether the dangling link is being created via symlink() or via dest().

For dangling links created via symlink(), the incoming Vinyl object represents the target, so its stats will determine the desired link type. If isDirectory() returns false then a 'file' link is created, otherwise a 'junction' or 'dir' link is created depending on the value of the useJunctions option.

For dangling links created via dest(), the incoming Vinyl object represents the link - typically loaded from disk via src(..., { resolveSymlinks: false }). In this case, the link type can't be reasonably determined and defaults to using 'file'. This may cause unexpected behavior when creating a dangling link to a directory. Avoid this scenario.

lastRun()

检索在当前运行进程中成功完成任务的最后一次时间。最有用的后续任务运行时,监视程序正在运行。当监视程序正在运行时,对于后续的任务运行最有用。

当与 src() 组合时,通过跳过自上次成功完成任务以来没有更 改的文件,使增量构建能够加快执行时间。

用法

const { src, dest, lastRun, watch } = require('gulp');
const imagemin = require('gulp-imagemin');

function images() {
  return src('src/images/**/*.jpg', { since: lastRun(images) })
    .pipe(imagemin())
    .pipe(dest('build/img/'));
}

exports.default = function() {
  watch('src/images/**/*.jpg', images);
};

函数原型

lastRun(task, [precision])

参数

参数类型描述
task (required)function string已注册任务的任务函数或字符串别名。
precisionnumber默认值: Node v0.10 版本中是 1000,在 Node v0.12+ 版本中是 0 。在下面的 时间戳精度 章中有详细说明。

返回值

返回一个时间戳(以毫秒为单位),表示任务的最后完成时间。如果任务尚未运行或已经失败,则返回 undefined

为了避免缓存无效状态(invalid state),因此,如果任务出错,则返回值为 undefined

可能出现的错误

当传入的参数值不是字符串或函数时,会抛出一个错误,并显示错误信息 "Only functions can check lastRun"。

当对不可扩展的函进行数调或 Node 不支持 WeakMap 时,会抛出一个错误,并显示错误信息 "Only extensible functions can check lastRun"。

时间戳精度

虽然时间戳的精度有合理的默认值,但仍然可以通过 precision 参数对其进行舍入。如果你所使用文件系统或 Node 版本对文件的时间属性精度不高的话,则很有用。

  • lastRun(someTask) 返回 1426000001111
  • lastRun(someTask, 100) 返回 1426000001100
  • lastRun(someTask, 1000) 返回 1426000001000

文件的 mtime stat 精度会由于 node 版本和/或所用的文件系统而出现误差。

平台精度
Node v0.101000ms
Node v0.12+1ms
FAT32 file system2000ms
HFS+ or Ext3 file systems1000ms
NTFS using Node v0.101s
NTFS using Node 0.12+100ms
Ext4 using Node v0.101000ms
Ext4 using Node 0.12+1ms

series()

将任务函数和/或组合操作组合成更大的操作,这些操作将按顺序依次执行。对于使用 series() 和 parallel() 组合操作的嵌套深度没有强制限制。

用法

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

function javascript(cb) {
  // body omitted
  cb();
}

function css(cb) {
  // body omitted
  cb();
}

exports.build = series(javascript, css);

函数原型

series(...tasks)

参数

参数类型描述
tasks (required)function string任意数量的任务函数都可以作为单独的参数传递。如果您以前注册过任务,可以使用字符串,但不建议这样做。

返回值

返回一个组合操作,它将注册为任务或嵌套在其他 series 和/或 parallel 组合中。

当执行组合操作时,所有任务将按顺序运行。如果一个任务中发生错误,则不会运行后续任务。

可能出现的错误

当没有传递任何任务时,抛出一个错误,并提示 "One or more tasks should be combined using series or parallel"。(一个或多个应该使用 series 或 parallel 组合的任务”。)

当传递无效的任务或未注册的任务时,将抛出一个错误,显示 "Task never defined"(任务从未定义)。

向前引用(Forward references)

向前引用是指使用尚未注册的字符串引用组合任务。在旧版本中,这是一种常见的实践,但是为了实现更快的任务运行时间和促进使用命名函数,删除了该特性。

在较新的版本中,如果尝试使用向前引用,将会得到一个错误,消息为 “Task never defined”。当您尝试为您的任务注册按字符串组合任务使用 exports 时,可能会遇到这种情况。在这种情况下,使用命名函数而不是字符串引用。

在迁移期间,您可能需要使用 forward reference registry。这将为每个任务引用添加一个额外的闭包,并显著降低构建速度。不要太长时间依赖这个修复程序

避免重复任务

当运行组合操作时,每个任务将在每次提供时执行。

在两个不同的组合中引用的 clean 任务将运行两次,将导致不期望的结果。因此,建议在最终组合中指定 clean 任务。

If you have code like this:

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

parallel()

将任务功能和/或组合操作组合成同时执行的较大操作。对于使用 series() 和 parallel() 进行嵌套组合的深度没有强制限制。

用法

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

function javascript(cb) {
  // body omitted
  cb();
}

function css(cb) {
  // body omitted
  cb();
}

exports.build = parallel(javascript, css);

函数原型

parallel(...tasks)

参数

参数类型注解
tasks (required)function string任意数量的任务函数都可以作为单独的参数传递。如果您以前注册过任务,可以使用字符串,但不建议这样做。

返回值

返回一个组合操作,它将注册为任务或嵌套在其他 series 和/或 parallel 组合中。

当执行组合操作时,所有任务将以最大并发性运行。如果一个任务发生错误,其他任务可能不确定地完成,也可能不完成。

可能出现的错误

当没有传递任何任务时,抛出一个错误,并提示 "One or more tasks should be combined using series or parallel"。(一个或多个应该使用 series 或 parallel 组合的任务”。)

当传递无效的任务或未注册的任务时,将抛出一个错误,显示 "Task never defined"(任务从未定义)。

向前引用(Forward references)

向前引用是指使用尚未注册的字符串引用组合任务。在旧版本中,这是一种常见的实践,但是为了实现更快的任务运行时间和促进使用命名函数,删除了该特性。

在较新的版本中,如果尝试使用向前引用,将会得到一个错误,消息为 “Task never defined”。当您尝试为您的任务注册按字符串组合任务使用 exports 时,可能会遇到这种情况。在这种情况下,使用命名函数而不是字符串引用。

在迁移期间,您可能需要使用 forward reference registry。这将为每个任务引用添加一个额外的闭包,并显著降低构建速度。不要太长时间依赖这个修复程序

避免重复任务

当一个组合操作执行时,每个任务(task)的每次调用都将被执行。

在两个不同的组合中引用的 clean 任务将运行两次,将导致不期望的结果。因此,建议在最终的组合中指定 clean 任务。

如果你有如下代码:

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

watch()

监听 globs 并在发生更改时运行任务。任务与任务系统的其余部分被统一处理。

用法

const { watch } = require('gulp');

watch(['input/*.js', '!input/something.js'], function(cb) {
  // body omitted
  cb();
});

函数原型

watch(globs, [options], [task])

参数

参数类型描述
globs (required)string array监听文件系统中匹配 Globs 的文件。
optionsobject详情参见下文 选项 。
taskfunction string一个 任务函数 或由 series() 和 parallel() 生成的组合任务。

返回值

chokidar 的一个实例,用于对监听设置进行细粒度控制。

可能出现的错误

当以 globs 形式传递非字符串或带有任何非字符串的数组时,将抛出一个错误,并提示 "Non-string provided as watch path"。

当字符串或数组作为 task 传递时,会抛出一个错误,提示 "watch task has to be a function (optionally generated by using gulp.parallel or gulp.series)"( watch 任务必须是一个函数(可以选择使用 gulp.parallel 或 gulp.series 生成))。

选项

名称类型默认值描述
ignoreInitialbooleantruefalse,则在实例化过程中调用该任务,以发现文件路径。用于在启动期间触发任务。 注意:  这个选项被传递给 chokidar,但默认为 true 而不是 false
delaynumber200文件更改和任务执行之间的毫秒延迟。允许在执行任务之前等待许多更改,例如查找和替换许多文件。
queuebooleantrue当为 true 且任务已经运行时,任何文件更改都将对单个任务执行进行排队。避免长时间运行的任务重叠。
eventsstring array[ 'add', 'change', 'unlink' ]正在监听的事件,以触发任务执行。可以是 'add''addDir''change''unlink''unlinkDir''ready'、和/或 'error'。 另外 'all' 也是可用的,它表示除 'ready' 和 'error' 之外的所有事件。 此选项被直接传递给 chokidar.
persistentbooleantrue如果为 false,监听器将不会保持 Node 进程的运行。不建议禁用此选项。 此选项被直接传递给 chokidar.
ignoredarray string RegExp functionDefines globs to be ignored. If a function is provided, it will be called twice per path - once with just the path, then with the path and the fs.Stats object of that file. 此选项被直接传递给 chokidar.
followSymlinksbooleantrue如果为 true,对符号链接和链接的文件的更改都将触发事件。如果为 false,则只有对符号链接的更改才触发事件。 此选项被直接传递给 chokidar.
cwdstring将与任何相对路径相结合以形成绝对路径的目录。对于绝对路径忽略。用于避免将 globs 与 path.join() 组合使用。 此选项被直接传递给 chokidar.
disableGlobbingbooleanfalse如果为 true,所有 globs 都被视为字面路径名称,即使它们具有特殊字符。 此选项被直接传递给 chokidar.
usePollingbooleanfalse当为 false 时,监听器将使用 fs.watch()(或 Mac 上的 fsevents)(或fsevents)进行监听。如果为 true,则使用 fs.watchFile() 轮询代替——这是通过网络或其他非标准情况成功监听文件所必需的。覆盖 useFsEvents 默认值。 此选项被直接传递给 chokidar.
intervalnumber100与 usePolling: true 配套使用。表示文件系统轮询的间隔。 此选项被直接传递给 chokidar.
binaryIntervalnumber300与 usePolling: true 配套使用。表示文件系统轮询二进制文件的间隔。 此选项被直接传递给 chokidar.
useFsEventsbooleantrue如果为 true 且 fsevents 可用, 则使用 fsevents 进行监听。如果显式设置为 true,则替代 usePolling 选项。如果设置为 false,则自动将 usePolling 设置为 true。 此选项被直接传递给 chokidar.
alwaysStatbooleanfalse如果为 true,总是调用 fs.stat() 对已更改的文件——将减慢文件监听器。只有在直接使用chokidar 实例时,fs.Stat 的对象才可用。 此选项被直接传递给 chokidar.
depthnumber指示将监听多少级嵌套目录。 此选项被直接传递给 chokidar.
awaitWriteFinishbooleanfalse不要使用这个选项,而是使用 delay此选项被直接传递给 chokidar.
ignorePermissionErrorsbooleanfalse设置为 true 以监听没有读取权限的文件。然而,如果由于 EPERM 或 EACCES 错误导致监听失败,则会自动跳过。 此选项被直接传递给 chokidar.
atomicnumber100只有在 useFsEvents 和 usePolling 为 false 时才激活。自动过滤掉一些编辑器从 "atomic writes" 中产生的工件。如果文件在删除后的指定毫秒内重新添加,则会发出一个更改事件(而不是取消链接然后添加)。 此选项被直接传递给 chokidar.

Chokidar 实例

watch() 方法返回 chokidar 的底层实例,提供对监听设置的细粒度控制。最常用来注册提供更改文件的 path 或 stats 的单个事件处理程序。

当直接使用 chokidar 实例时,您将无法访问任务系统集成,包括异步完成、队列和延迟

const { watch } = require('gulp');

const watcher = watch(['input/*.js']);

watcher.on('change', function(path, stats) {
  console.log(`File ${path} was changed`);
});

watcher.on('add', function(path, stats) {
  console.log(`File ${path} was added`);
});

watcher.on('unlink', function(path, stats) {
  console.log(`File ${path} was removed`);
});

watcher.close();

watcher.on(eventName, eventHandler)

注册 eventHandler 函数,当指定的事件发生时调用该函数。

参数类型描述
eventNamestring可以观察到的事件有 'add''addDir''change''unlink''unlinkDir''ready''error'、 或 'all'.
eventHandlerfunction当指定的事件发生时调用的函数。参数详见下表。
参数类型描述
pathstring已更改的文件的路径。如果设置了 cwd 选项,则是通过删除 cwd 的相对路径。
statsobject一个 fs.Stat 对象,但可以是 undefined。如果 alwaysStat 选项被设置为 truestats 将始终被提供。

watcher.close()

关闭文件监听器。一旦关闭,就不会再发出任何事件。

watcher.add(globs)

向已经运行的监听器实例添加额外的 globs。

参数类型描述
globsstring array额外需要监听的 glob。

watcher.unwatch(globs)

删除正在被监听的 globs,而监视程序继续使用剩余的路径。

参数类型描述
globsstring array要删除的 glob。

task()

提醒: 这个API不再是推荐的模式了 - export your tasks。因此就不翻译了!

在任务系统中定义任务。然后可以从命令行和 series()parallel() 和 lastRun() api 访问该任务。

Usage

Register a named function as a task:

const { task } = require('gulp');

function build(cb) {
  // body omitted
  cb();
}

task(build);

Register an anonymous function as a task:

const { task } = require('gulp');

task('build', function(cb) {
  // body omitted
  cb();
});

Retrieve a task that has been registered previously:

const { task } = require('gulp');

task('build', function(cb) {
  // body omitted
  cb();
});

const build = task('build');

Signature

task([taskName], taskFunction)

Parameters

If the taskName is not provided, the task will be referenced by the name property of a named function or a user-defined displayName property. The taskName parameter must be used for anonymous functions missing a displayName property.

Since any registered task can be run from the command line, avoid using spaces in task names.

parametertypenote
taskNamestringAn alias for the task function within the task system. Not needed when using named functions for taskFunction.
taskFunction (required)functiontask function or composed task - generated by series() and parallel(). Ideally a named function. Task metadata can be attached to provide extra information to the command line.

Returns

When registering a task, nothing is returned.

When retrieving a task, a wrapped task (not the original function) registered as taskName will be returned. The wrapped task has an unwrap() method that will return the original function.

Errors

When registering a task where taskName is missing and taskFunction is anonymous, will throw an error with the message, "Task name must be specified".

Task metadata

propertytypenote
namestringA special property of named functions. Used to register the task. Note:  name is not writable; it cannot be set or changed.
displayNamestringWhen attached to a taskFunction creates an alias for the task. If using characters that aren't allowed in function names, use this property.
descriptionstringWhen attached to a taskFunction provides a description to be printed by the command line when listing tasks.
flagsobjectWhen attached to a taskFunction provides flags to be printed by the command line when listing tasks. The keys of the object represent the flags and the values are their descriptions.
const { task } = require('gulp');

const clean = function(cb) {
  // body omitted
  cb();
};
clean.displayName = 'clean:all';

task(clean);

function build(cb) {
  // body omitted
  cb();
}
build.description = 'Build the project';
build.flags = { '-e': 'An example flag' };

task(build);

registry()

允许将自定义的注册表插入到任务系统中,以便提供共享任务或增强功能。

注意:  只有用 task() 方法注册的任务才会进入自定义注册表中。直接传递给 series() 或 parallel() 的任务函数(task functions)不会进入自定义任务注册表 - 如果你需要自定义注册表的行为,请通过字符串引用的方式将任务(task)组合在一起。

分配新注册表时,将传输当前注册表中的每个任务,并将用新注册表替换当前注册表。这允许按顺序添加多个自定义注册表。

有关详细信息,请参考 创建自定义注册表 。

用法

const { registry, task, series } = require('gulp');
const FwdRef = require('undertaker-forward-reference');

registry(FwdRef());

task('default', series('forward-ref'));

task('forward-ref', function(cb) {
  // body omitted
  cb();
});

函数原型

registry([registryInstance])

参数

参数类型注解
registryInstanceobject自定义注册表的实例(而不是类)。

返回值

如果传递了 registryInstance,则不会返回任何内容。如果没有传递参数,则返回当前注册表实例。

可能出现的错误

参数错误

当构造函数(而不是实例)作为 registryInstance 传递时,将抛出一个错误(error),并且提示信息如下:

Custom registries must be instantiated, but it looks like you passed a constructor.(自定义注册表必须实例化,但看起来你传递的是一个构造函数啊,亲!)

缺少 get 方法

当一个没有 get 方法的注册表作为 registryInstance 传递时,将抛出一个错误(error),并且提示信息如下:

Custom registry must have get function.(自定义注册表必须具备 get 函数。)

缺少 set 方法

当一个没有 set 方法的注册表作为 registryInstance 传递时,将抛出一个错误(error),并且提示信息如下:

Custom registry must have set function.(自定义注册表必须具有 set 函数。)

缺少 init 方法

当一个没有 init 方法的注册表作为 registryInstance 传递时,将抛出一个错误(error),并且提示信息如下:

Custom registry must have init function"(自定义注册表必须具有 init 函数。)

缺少 tasks 方法

当一个没有 tasks 方法的注册表作为 registryInstance 传递时,将抛出一个错误(error),并且提示信息如下:

Custom registry must have tasks function.(自定义注册表必须具有 tasks 函数。)

tree()

获取当前任务依赖关系树——在极少数情况下需要它。

通常,gulp 使用者不会使用 tree(),但它是公开的,因此 CLI 可以显示在 gulpfile 中定义的任务的依赖关系图。

用法

Example gulpfile:


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

function one(cb) {
  // body omitted
  cb();
}

function two(cb) {
  // body omitted
  cb();
}

function three(cb) {
  // body omitted
  cb();
}

const four = series(one, two);

const five = series(four,
  parallel(three, function(cb) {
    // Body omitted
    cb();
  })
);

module.exports = { one, two, three, four, five };

tree() 的输出:

{
  label: 'Tasks',
  nodes: [ 'one', 'two', 'three', 'four', 'five' ]
}

tree({ deep: true }) 的输出:

{
  label: "Tasks",
  nodes: [
    {
      label: "one",
      type: "task",
      nodes: []
    },
    {
      label: "two",
      type: "task",
      nodes: []
    },
    {
      label: "three",
      type: "task",
      nodes: []
    },
    {
      label: "four",
      type: "task",
      nodes: [
        {
          label: "<series>",
          type: "function",
          branch: true,
          nodes: [
            {
              label: "one",
              type: "function",
              nodes: []
            },
            {
              label: "two",
              type: "function",
              nodes: []
            }
          ]
        }
      ]
    },
    {
      label: "five",
      type: "task",
      nodes: [
        {
          label: "<series>",
          type: "function",
          branch: true,
          nodes: [
            {
              label: "<series>",
              type: "function",
              branch: true,
              nodes: [
                {
                  label: "one",
                  type: "function",
                  nodes: []
                },
                {
                  label: "two",
                  type: "function",
                  nodes: []
                }
              ]
            },
            {
              label: "<parallel>",
              type: "function",
              branch: true,
              nodes: [
                {
                  label: "three",
                  type: "function",
                  nodes: []
                },
                {
                  label: "<anonymous>",
                  type: "function",
                  nodes: []
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

函数原型

tree([options])

参数

参数类型描述
optionsobject详情请见下文 选项 。

返回值

返回一个详细描述已注册的任务树的对象——包含具有 'label' 和 'nodes' 属性的嵌套对象(与 archy 兼容)。

每个对象可能有一个 type 属性,用于确定节点是 task 还是 function

每个对象可能有一个 branch 属性,当 true 时,该属性指示节点是使用 series() 还是 parallel() 创建的。

选项

nametypedefaultnote
deepbooleanfalse如果为 true,则返回整个树。如果为 false,则只返回顶级任务。

Vinyl

虚拟的文件格式。当 src() 读取文件时,将生成一个 Vinyl 对象来表示文件——包括路径、内容和其他元数据。

Vinyl 对象可以使用插件进行转换。还可以使用 dest() 将它们持久化到文件系统。

当创建您自己的 Vinyl 对象时——而不是使用 src() 生成——使用外部 vinyl 模块,如下面的用法所示。

用法

const Vinyl = require('vinyl');

const file = new Vinyl({
  cwd: '/',
  base: '/test/',
  path: '/test/file.js',
  contents: new Buffer('var x = 123')
});

file.relative === 'file.js';

file.dirname === '/test';
file.dirname = '/specs';
file.path === '/specs/file.js';

file.basename === 'file.js';
file.basename = 'file.txt';
file.path === '/specs/file.txt';

file.stem === 'file';
file.stem = 'foo';
file.path === '/specs/foo.txt';
file.extname === '.txt';
file.extname = '.js';
file.path === '/specs/foo.js';

函数原型

new Vinyl([options])

参数

参数类型描述
optionsobject详情请参加下文 选项

返回值

返回一个 Vinyl 类的实例,表示一个单独的虚拟文件,详见下面的 Vinyl 实例

可能出现的错误

当传递的任何选项都不符合表中定义的实例属性定义(如 path 被设置为一个数字)时抛出错误。

选项

名称类型默认住
cwdstringprocess.cwd()此目录用于推导相对路径。该目录路径将被 规范化(normalized) 并删除结尾的目录分隔符。
basestring用于计算 relative 实例属性。 如果没有设置,则回退到 cwd 的值。通常设置为 glob base。该路径将被 规范化(normalized) 并删除结尾的目录分隔符。
pathstring完整的绝对文件路径。该路径将被 规范化(normalized) 并删除结尾的目录分隔符。
historyarray[ ]此路径数组用于预先填充 Vinyl 实例的 history 属性。通常是从先前的 Vinyl 对象衍生处新的 Vinyl 对象。通过同时传递了 path 和 history 参数,则将path 附加到 history 后面。数组中的每个条目都将被 规范化(normalized) 并删除结尾的目录分隔符。
statobject一个 fs.Stats 实例,通常是对文件调用 fs.stat() 的结果。用于确定 Vinyl 对象是否表示一个目录或符号链接。
contentsReadableStream Buffer nullnull文件的内容。如果 contents 是一个 ReadableStream,它将被包装进一个 cloneable-readable 流(stream
)中。

options 上的任何其他属性都将直接分配给 Vinyl 实例。

const Vinyl = require('vinyl');

const file = new Vinyl({ foo: 'bar' });
file.foo === 'bar';

Vinyl 实例

每个 Vinyl 对象实例都具有访问和/或修改虚拟文件信息的属性和方法。

实例属性

所有内部管理的路径——除了 contents 和 stat 之外的任何实例属性——都被规范化,并删除了末尾分隔符。有关更多信息,请参见规范化和连接

属性类型描述抛出错误
contentsReadableStream Buffer null获取和设置虚拟文件的内容。如果将其设置为 ReadableStream,它将被包装在一个 cloneable-readable 流(stream)中。如果设置为 ReadableStream、Buffer 或 null 之外的任何值。
statobject获取或设置 fs.Stats 的实例。当确定 Vinyl 对象是否表示目录或符号链接时使用。
cwdstring获取并设置当前工作目录。用于推导相对路径。如果设置为空字符串或任何非字符串值。
basestring获取和设置起始目录(base directory)。用于计算实例的 relative 属性。在由 src() 生成的 Vinyl 对象上,将设置为 glob base。如果设置为 null 或 undefined,则会用实例属性 cwd 的值来代替。如果设置为空字符串或任何非字符串值(null 或 undefined 除外)。
pathstring获取和设置完整的绝对文件路径。设置为与当前 path 不同的值会将新路径附加到 history 实例属性中。如果设置为任何非字符串值。
historyarray已分配的 Vinyl 对象的所有 path 值的数组。第一个元素是原始路径,最后一个元素是当前路径。此属性及其包含元素应被视为只读,仅通过设置 path 实例属性间接更改。
relativestring获取 base 和 path 实例属性之间的相对路径段。如果设置为任何值。如果在 path 不可用时访问。
dirnamestring获取和设置 path 实例属性的目录。如果在 path 不可用时访问。
stemstring获取和设置 path 实例属性的不带扩展名的文件名。如果在 path 不可用时访问。
extnamestring获取和设置 path 实例属性的扩展名。如果在 path 不可用时访问。
basenamestring获取和设置 path 实例属性的文件名(stem + extname)。如果在 path 不可用时访问。
symlinkstring获取和设置符号链接的引用路径。如果设置为任何非字符串值。

实例方法

方法返回值类型返回值
isBuffer()boolean如果 contents 实例属性是一个 Buffer,则返回 true。
isStream()boolean如果 contents 实例属性是一个 Stream,则返回 true。
isNull()boolean如果 contents 实例属性为 null,则返回 true。
isDirectory()boolean如果实例表示一个目录,则返回 true。当 isNull() 返回 true,stat 实例属性是一个对象,并且 stat.isDirectory() 返回 true 时,实例被认为是一个目录。这假设 Vinyl 对象是用一个有效的(或适当模拟的) fs.Stats 对象构造的。
isSymbolic()boolean如果实例表示符号链接,则返回 true。 当 isNull() 返回 true,stat 实例属性是一个对象,并且 stat.isSymbolicLink() 返回 true 时, 实例被认为是 symbolic。 这假设 Vinyl 对象是用一个有效的(或适当模拟的) fs.Stats 对象构造的。
clone([options])objectA new Vinyl object with all properties cloned. 一个使用所有属性克隆出的新的 Vinyl 对象。 默认情况下,自定义属性是深拷贝。如果 deep 选项为 false,自定义属性将被浅拷贝。如果 contents 选项设置为 fasle 并且 contents 属性是一个 Buffer,那么这个 Buffer 将被复用,而不是克隆。
inspect()string返回 Vinyl 对象的格式化说明。由 Node 的 console.log 自动调用。

路径规范化和连接

所有路径属性都由它们的 setter 进行规范化。使用 / 连接路径,而不是使用 path.join(),这样就可以在所有平台上正确地进行规范化。永远不要使用  连接。(  是 POSIX 系统上的一个有效文件名字符。)

const file = new File();
file.path = '/' + 'test' + '/' + 'foo.bar';

console.log(file.path);
// posix => /test/foo.bar
// win32 => \test\foo.bar

Vinyl.isVinyl()

检测一个对象(object)是否是一个 Vinyl 实例。不要使用 instanceof 方法。

注意:此方法使用了 Vinyl 的一个内部属性,而这个属性在老版本的 Vinyl 中是不存在的,如果你使用的恰好时老版本,则会得到一个 fasle 结果。

用法

const Vinyl = require('vinyl');

const file = new Vinyl();
const notAFile = {};

Vinyl.isVinyl(file) === true;
Vinyl.isVinyl(notAFile) === false;

函数原型

Vinyl.isVinyl(file);

参数

参数类型注解
fileobject需要检查的对象

返回值

如果 file 对象是 Vinyl 实例则返回 true。

Vinyl.isCustomProp()

确定一个属性是否由 Vinyl 在内部进行管理。Vinyl 在构造函数中设置值或在 clone() 实例方法中复制属性时使用。

这种方法在扩展 Vinyl 类时很有用。详情参见下文:扩展 Vinyl

用法

const Vinyl = require('vinyl');

Vinyl.isCustomProp('sourceMap') === true;
Vinyl.isCustomProp('path') === false;

函数原型

Vinyl.isCustomProp(property)

参数

参数类型注解
propertystring要检查的属性名。

返回值

如果属性不是内部管理的,则为 True。

扩展 Vinyl

当在内部管理自定义属性时,必须扩展静态 isCustomProp 方法,并在查询其中一个自定义属性时返回 false。

const Vinyl = require('vinyl');

const builtInProps = ['foo', '_foo'];

class SuperFile extends Vinyl {
  constructor(options) {
    super(options);
    this._foo = 'example internal read-only value';
  }

  get foo() {
    return this._foo;
  }

  static isCustomProp(name) {
    return super.isCustomProp(name) && builtInProps.indexOf(name) === -1;
  }
}

在上面的例子中,foo 和 _foo 在克隆或将 options 传递给 new SuperFile(options) 时不会分配给新对象。

如果您的自定义属性或逻辑在克隆期间需要特殊处理,请在扩展 Vinyl 时覆盖 clone 方法。