gulp学习笔记

快速入门

检查 node、npm 和 npx 是否正确安装

node --version
npm --version
npx --version

安装 gulp 命令行工具

npm install --global gulp-cli

创建项目目录并进入

npx mkdirp my-project
cd my-project

在项目目录下创建 package.json 文件

npm init

设置项目名、版本、描述信息等。

安装 gulp,作为开始时依赖项

npm install --save-dev gulp

检查 gulp 版本

gulp --version

确保输出与下面的屏幕截图匹配,否则你可能需要执行本指南中的上述步骤。
gulp --version

创建 gulpfile 文件

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

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

exports.default = defaultTask;

测试

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

gulp

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

输出结果

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

javascript 和 Gulpfile

Gulpfile 详解

gulpfile 是项目目录下名为 gulpfile.js(或者首字母大写 Gulpfile.js,就像 Makefile 一样命名)的文件,在运行 gulp命令时会被自动加载。在这个文件中,你经常会看到类似src()dest()series()parallel() 函数之类的 gulp API,除此之外,纯 JavaScript 代码或 Node 模块也会被使用。任何导出(export)的函数都将注册到 gulp 的任务(task)系统中。

Gulpfile 转译

可以使用TypeScript 或 Babel,通过修改 gulpfile.js 文件的扩展名来表明所用的编程语言并安装对应的转译模块。

  • 对于 TypeScript,重命名为 gulpfile.ts并安装 ts-node 模块
  • 对于 Babel,重命名为 gulpfile.babel.js 并安装 @babel/register模块。

针对此功能的高级知识和已支持的扩展名的完整列表,请参考 gulfile 转译 文档。

Gulpfile 分割

随着文件的变大,可以将此文件重构为数个独立的文件。
每个任务(task)可以被分割为独立的文件,然后导入(import)到 gulpfile 文件中并组合。这不仅使事情变得井然有序,而且可以对每个任务(task)进行单独测试,或者根据条件改变组合。

Node 的模块解析功能允许你将 gulpfile.js文件替换为同样命名为 gulpfile.js的目录,该目录中包含了一个名为 index.js的文件,该文件被当做 gulpfile.js使用。并且,该目录中还可以包含各个独立的任务(task)模块。

创建任务(task)

每个 gulp 任务(task)都是一个异步的 JavaScript 函数,此函数是一个可以接收 callback 作为参数的函数,或者是一个返回 stream、promise、event emitter、child process 或 observable 类型值得函数。由于某些平台的限制而不支持一步任务,因此 gulp 还提供了一个漂亮 替代品

导出任务

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

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

一个私有(private)类型的任务(task)在外观和行为上和其他任务(task)是一样的,但是不能够被用户直接调用。如需将一个任务(task)注册为公开(public)类型的,只需从 gulpfile 中 导出(export)即可。

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

// `clean` 函数并未被导出(export),因此被认为是私有任务(private task)。
// 它仍然可以被用在 `series()`组合中。
function clean(cb) {
    // body omitted
    cb();
}

// `build` 函数被导出(export)了,因此它是一个公开任务(public task),并且可以被 `gulp`命令直接调用。
// 它也仍然可以被用在 `series()`组合中。
function build(cb) {
    // body omitted
    cb();
}

exports.build = build;
exports.default = series(clean, build);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDa7U7Eh-1638152543671)(https://gulpjs.com/img/docs-gulp-tasks-command.png)]

在以前的 gulp 版本中,task()方法用来将函数注册为任务(task)。虽然这个 API 依旧是可以使用的,但是 导出(export)将会是主要的注册机制,除非遇到 export 不起作用的情况。

组合任务

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

如果需要让任务(task)按顺序执行,请使用 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);

series()parallel被调用时,任务(tasks)被立即组合在一起。这就允许在组合中进行改变,而不需要在单个任务(task)中进行条件判断。

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
)

当一个组合操作执行时,这个组合中的每一个任务每次被调用时都会被执行。例如,在两个不同的任务(task)之间调用的clean任务(task)将会被执行两次,并且将导致不可预期的结果。因此,最好重构组合中的 clean任务(task)。
如果你有如下代码:

// 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, paralle(css, javascript))

异步执行

Node 库以多种方式处理异步功能。最常见的模式是 error-first callbacks,但是你还可能遇到 streamspromisesevent emitterschild processes、或 observables。gulp 任务(task)规范化了所有这些类型的异步功能。

任务(task)完成通知

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

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

返回 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() {
    returrn Observable.of(1,2,3);
}

exports.default = observableTask;

使用 callback

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

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

exports.default = callbackTask;

如需通过 callback 把任务中的错误告知 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;

gulp 不再支持同步任务(Synchronous tasks)

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

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

使用 async/await

你还可以将任务定义为一个 async 函数,它利用 promsie 对你的任务进行包装。这将允许你使用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 参数,并从文件系统中读取文件然后生成一个 Node 流(stream)。它将所有匹配的文件读取到内存中并通过流(stream)进行处理。

src()产生的流(stream)应当从任务(task)中返回并发出异步完成的信号,就如 创建任务(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(),但是创建的是链接而不是文件。

大多数情况下,利用 .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)中创建未压缩(unmiified)和已压缩(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()bufferread 参数进行设置。

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

Glob 详解

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

src() 方法接受一个 glob 字符串或由多个 glob 字符创组成的数组作为参数,用于确定哪些文件需要被操作。glob 或 glob 数组必须至少匹配到一个匹配项,否则 src() 将报错。当使用glob 数组时,将按照每个 glob 在数组中的位置一次执行匹配-这尤其对于取反(negative)glob 有用。

字符创片段与分隔符

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

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

'glob_with-uncommon_\\*_character.js'

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

const invalidGlob = path.join(__dirname, 'src/*.js');

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

在一个字符串片段中匹配任意数量的字符,包括零个匹配。对于匹配单级目录下的文件很有用。
下面这个 glob 能够匹配类似 index.js 的文件,但是不能匹配类似 scripts/index.jsscripts/nested/index.js 的文件。

'*.js'

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

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

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

'scripts/**/*.js'

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

特殊字符:!(取反)

由于 glob 匹配时是按照每个 glob 在数组中的位置依次进行匹配操作的,所以 glob 数组中的取反(negative)glob 必须跟在一个非取反(non-negative)的 glob 后面。第一个 glob 匹配到一组匹配项,然后后面的取反 glob 删除这些匹配项中的一部分。如果取反 glob 只是由普通字符组成的字符创,则执行效率是最高的。

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

如果任何非取反(non-negative)的 glob 跟随者一个取反(negative)glob,任何匹配项都不会被删除。

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

在上面的示例中,如果取反(negative)glob 是 !node_modules/**/*.js,那么各匹配项都必须与取反 glob 进行比较,这将导致执行速度极慢。

匹配重叠(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` 任务中工作的很好
exports.default = async function() {
    const bundle = await rollup.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)
        }
        cb(null, file);
    }))
    .pipe(dest('outoput/'));
}

文件监控

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

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

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

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

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

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

// 可以只关联一个任务
watch('src/*.css', css);
// 或者关联一个任务组合
watch('src/*.js', series(clean, javascript));

警告:避免同步任务

与文件监控程序关联的任务不能是同步任务。如果你将同步任务关联到监控程序,则无法确认任务的完成情况,任务将不会再次运行(假定当前正在运行)。

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

可监控的事件

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

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

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

初次执行

调用 watch() 之后,关联的任务是不会被立即执行的,而是要等到第一次文件修改之后才执行。
如需在第一次文件修改之前执行,也就是调用 watch() 之后立即执行,请将 ignoreInitial 参数设置为 false

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

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

队列

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

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

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

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

延迟

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

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

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

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

概念

Vinyl

Vinyl 是描述文件的元数据对象。Vinyl示例的主要属性是文件系统中文件核心的 pathcontents 核心方面。Vinyl 对象可用于描述来自多个源的文件(本地文件系统或任何远程存储选项上)。

Vinyl 适配器

Vinyl 提供了一种描述文件的方法,但是需要一种访问这些文件的方法。使用 Vinyl 适配器访问每个文件源。

适配器暴露了:

  • 一个签名为 src(globs, [options]) 的方法,返回一个生成 Vinyl 对象的流。
  • 一个带有签名为 dest(folder, [options]) 的方法,返回一个使用 Vinyl 对象的流。
  • 任何特定于其输入/输出媒体的额外方法-例如 symlink 方法 vinyl-fs 所提供的。它们应该总是返回产生和/或消耗 Vinyl 对象的流。

任务

每个 gulp 任务都是一个异步 JavaScript 函数,它要么接受一个错误优先回调,要么返回一个流、promise、事件发射器、子进程或 observable。由于一些平台限制,不支持同步任务。

Globs

glob 是一串文字和/或通配符,如 ***,或 !,用于匹配文件路径。Globing 是使用一个或多个 globs 在文件系统上定位文件的操作。

Glob base

glob base(有时成为 glob parent)是 glob 字符串中任何特殊字符之前的路径段。因此,/src/js/**.js 的 glob base 是 /src/js。所有匹配 glob 的路径都保证共享 glob base ---- 该路径不能是可变的。

src() 生成的 Vinyl 实例是用 glob base 集作为它们的 base 属性构造的。当使用 dest()写入文件系统时,将从输出路径中删除 base,以保留目录结构。

文件系统统计数据

文件元数据作为 Node 的 fs.Stats 实例提供。它是 Vinyl 实例的 stat 属性,并在内部用于确定 Vinyl 对象是否表示目录或符号链接(symbolic link)。当写入文件系统时,权限和时间值将从 Vinyl 对象的 stat 属性同步。

文件系统模式

文件系统模式决定文件的权限。文件系统上的大多数文件和目录将具有相当宽松的模式,允许 gulp 代表您读取/写入/更新文件。默认情况下,gulp 将创建与运行进程具有相同权限的文件,但是您可以通过 src()dest() 中的选线配置模式。如果您遇到权限(EPERM)问题,请检查文件上的模式。

模块

Gulp 由许多小模块组成,这些模块被拉到一起以实现内聚性工作。通过在小模块中使用 semver,我们可以在不发布 gulp 新版本的情况下发布 bug 修复和特性。通常,当您没有看到 主存储库上的进站时,工作是在其中一个模块中完成的。

如果遇到问题,请使用 npm update 命令更细当前模块。如果问题仍然存在,则在单个项目存储库上打开一个 issus。

  • undertaker - the task registration system
  • vinyl - the virtual file objects
  • vinyl-fs - a vinyl adapter to your local file system
  • glob-watcher - the file watcher
  • bach - task orchestration using series() and parallel()
  • last-run - tracks the last run time of a task
  • vinyl-sourcemap - built-in sourcemap support
  • gulp-cli - the command line interface for interacting with gulp\

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 arrayGlobs to watch on the file system
optionsobject在下面的选项中详细说明

返回值

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

可能出现的错误

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

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

选项

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

名称类型默认值描述
bufferboolean functiontrue当为 true 时,文件内容被缓冲到内存中。如果为 false,Vinyl 对象的 contents 属性将是一个暂停流。可能无法缓冲大文件的内容。注意:插件可能不支持流媒体内容。
readboolean functiontrue如果为 false,文件将不会被读取,并且它们的 Vinyl 对象将不能通过 .dest() 写入磁盘
sincedate timestamp function设置时,仅为自指定时间以来修改的文件创建 Vinyl 对象。

资源映射

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

内部资源映射:

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(required)string function将写入文件的输出目录的路径。如果使用一个函数,该函数将与每个 Vinyl 对象一起调用,并且必须返回一个字符串目录路径。
optionsobject详情见下文选项

返回值

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

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

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

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

可能出现的错误

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

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

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

选项

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

名称类型默认值注解
cwdstring functionprocess.cwd()这个目录将与任何相对路径相结合形成绝对路径。对于绝对路径忽略。用于避免将 directory 与 path.join() 相结合。
modenumber functionstat.mode of the Vinyl object创建文件时使用的模式。如果没有设置,并且缺少 stat.mode,则使用 process’ 模式
dirModenumber function创建目录时使用的模式。如果没有设置,将使用 process’ 模式
overwriteboolean functiontrue如果为 true,则用相同的路径覆盖现有文件。
appendbooleanfalse如果为true,则将内容添加到文件末尾,而不是替换现有内容。
sourcemapsboolean string functionfalse如果为true,则将内联 sourcemaps 写入输出文件。指定一个 string 路径将在给定路径上写入外部 sourcemaps
relativeSymlinksboolean functionfalse当为false时,创建的任何符号链接将是绝对的。注意:如果正在创建连接,则忽略它们,因为他们必须是绝对的。
useJunctionsboolean functiontrue此选项仅适用于 Windows,在其他地方被忽略。当为 true 时,创建目录符号链接作为连接(junction)。

元数据更新

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

在不支持 Node 的 process.getuid() 或 process.geteuid() 方法的 windows 或其他操作系统上禁用此功能。

注意:fs.futimes() 在内部将 mtimeatime 时间戳转换为秒,导致 32 位操作系统的精度有所下降。

Sourcemaps

Sourcemaps 支持直接构建到 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: '.' }))

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.

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。

series()

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

用法

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

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

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

exports.build = series(javacript, css);

函数原型

series(...tasks)

参数

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

返回值

返回一个组合操作,它将注册为任务或嵌套在其他 series 和/或 parallel 组合中。
当执行组合操作时,所有任务将按顺序运行。如果一个任务中发生错误,则不会运行后续任务。

parallel()

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

用法

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

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

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

exports.build = parallel(javascript, css);

函数原型

parallel(...tasks)

参数

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

返回值

返回一个组合操作,它将注册为任务或嵌套早其他 series 和/或 parallel 组合中。
当执行组合操作时,所有任务将以最大并发性运行。如果一个任务发生错误,其他任务可能不确定地完成,也可能不完成。

watch()

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

用法

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

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

函数原型

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

参数

参数类型描述
globs(required)string arrayGlobs 用来监听文件系统
optionsobject详情参见下文选项
taskfunction string一个任务函数或由 series()parallel() 生成的组合任务

返回值

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 选项被设置为 true,stats 将始终被提供

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

watcher.add(globs)
向已经运行的监听器示例添加额外的 globs。

参数类型描述
globsstring array额外的要监听的 globs

watcher.unwatch(globs)

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

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

task()

提醒:这个API 不再是推荐的模式了。
在任务系统中定义任务。然后可以从命令行和 series()parallel()lastRun() api 访问该任务。

使用

将命名函数注册为任务。

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

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

task(build);

将匿名函数注册为任务。

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

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

检索先前已注册的任务。

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

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

const build = task('build');

函数原型

task([taskName], taskFunction)

参数

如果未提供 taskName,则任务将由命名函数的 name 属性或用户定义的 displayName 属性引用。taskName 参数必须用于缺少 displayName 属性的匿名函数。

由于任何注册任务都可以从命令行运行,避免在任务名称中使用空格。

参数类型描述
taskNamestring任务系统中任务函数的别名。在为 taskFunction 使用命名函数时不需要。
taskFunction(required)function任务函数或组合任务–由 series() 和 parallel() 生成。最好是一个命名函数。可以附加任务元数据,为命令行提供额外信息。

返回值

注册任务时,不返回任何内容。

当检索任务时,将返回注册为 taskName 的包装任务(不是原始函数)。包装的任务有一个unwrap() 方法,将返回原始函数。

任务元信息

属性类型描述
namestring命名函数的特殊属性。用于注册任务。注意:名称不可写的;它不能被设置或改变
displayNamestring当附加到 taskFunction 时,会为 任务创建一个别名。如果使用函数名称中不允许的字符,请使用此舒心。
descriptionstring当附加到 taskFunction 时,它提供了在列出任务时由命令函打印的描述。
flagsobject当连接到一个taskFunction时,提供了在列出任务时由命令行打印的标志。该对象的键代表标志,值是它们的描述。
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);

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值