快速入门
检查 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
确保输出与下面的屏幕截图匹配,否则你可能需要执行本指南中的上述步骤。
创建 gulpfile 文件
利用任何文本编辑器在项目的根目录下创建一个名为 gulpfile.js 的文件,并在文件中输入一下内容。
function defaultTask(cb) {
// place code for your default task here
cb();
}
exports.default = defaultTask;
测试
在项目根目录下执行 gulp 命令:
gulp
如需运行多个任务(task),可以执行 gulp <task> <othertask>
输出结果
默认任务(task)将执行,因为任务为空,因此没有实际动作。
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,但是你还可能遇到 streams、promises、event emitters、child 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()
的 buffer
和 read
参数进行设置。
- 缓冲(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.js
或 scripts/nested/index.js
的文件。
'*.js'
特殊字符:**(两个星号)
在多个字符串片段中匹配任意数量的字符,包括零个匹配。对于匹配嵌套目录下的文件很有用。请确保适当地限制带有两个星号的 glob 的使用,以避免匹配大量不必要的目录。
下面这个 glob 被适当地限制在 scripts/
目录下。它将匹配类似 scripts/index.js
、scripts\nested/index.js
和 scripts/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示例的主要属性是文件系统中文件核心的 path
和 contents
核心方面。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])
参数
参数 | 类型 | 描述 |
---|---|---|
globs | string array | Globs to watch on the file system |
options | object | 在下面的选项中详细说明 |
返回值
返回一个可以在管道的开始或中间使用的流,用于根据给定的 globs 添加文件。
可能出现的错误
当 globs 参数只能匹配一个文件(如 foo/bar.js)而且没有找到匹配时,会抛出一个错误,提示 “File not found with singular glob”。若要抑制此错误,请将 allowEmpty
选项设置为 true。
当在 globs 中给出一个无效的 glob 时,抛出一个错误,并显示 “Invalid glob argument”
选项
对于接受函数的选项,传递的函数将与每个 Vinyl 对象一起调用,并且必须返回另一个列出类型的值。
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
buffer | boolean function | true | 当为 true 时,文件内容被缓冲到内存中。如果为 false,Vinyl 对象的 contents 属性将是一个暂停流。可能无法缓冲大文件的内容。注意:插件可能不支持流媒体内容。 |
read | boolean function | true | 如果为 false,文件将不会被读取,并且它们的 Vinyl 对象将不能通过 .dest() 写入磁盘 |
since | date 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 对象一起调用,并且必须返回一个字符串目录路径。 |
options | object | 详情见下文选项 |
返回值
返回一个可以在管道的中间或末尾使用的流,用于在文件系统上创建文件。
每当 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 对象一起调用,并且必须返回另一个列出类型的值。
名称 | 类型 | 默认值 | 注解 |
---|---|---|---|
cwd | string function | process.cwd() | 这个目录将与任何相对路径相结合形成绝对路径。对于绝对路径忽略。用于避免将 directory 与 path.join() 相结合。 |
mode | number function | stat.mode of the Vinyl object | 创建文件时使用的模式。如果没有设置,并且缺少 stat.mode,则使用 process’ 模式 |
dirMode | number function | 创建目录时使用的模式。如果没有设置,将使用 process’ 模式 | |
overwrite | boolean function | true | 如果为 true,则用相同的路径覆盖现有文件。 |
append | boolean | false | 如果为true,则将内容添加到文件末尾,而不是替换现有内容。 |
sourcemaps | boolean string function | false | 如果为true,则将内联 sourcemaps 写入输出文件。指定一个 string 路径将在给定路径上写入外部 sourcemaps |
relativeSymlinks | boolean function | false | 当为false时,创建的任何符号链接将是绝对的。注意:如果正在创建连接,则忽略它们,因为他们必须是绝对的。 |
useJunctions | boolean function | true | 此选项仅适用于 Windows,在其他地方被忽略。当为 true 时,创建目录符号链接作为连接(junction)。 |
元数据更新
每当 dest() 流创建一个文件时,就会将 Vinyl 对象的 mode、mtime和 atime 与创建的文件进行比较。如果它们不同,创建的文件将被更新以反映 Vinyl 对象的元数据。如果这些属性相同,或者 gulp 没有更改的权限,则会跳过该尝试。
在不支持 Node 的 process.getuid() 或 process.geteuid() 方法的 windows 或其他操作系统上禁用此功能。
注意:
fs.futimes()
在内部将mtime
和atime
时间戳转换为秒,导致 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 function | The 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. |
options | object | Detailed 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 | 已注册任务的任务函数或字符串别名。 |
precision | number | 默认值: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 array | Globs 用来监听文件系统 |
options | object | 详情参见下文选项 |
task | function 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
函数,当指定的事件发生时调用该函数。
参数 | 类型 | 描述 |
---|---|---|
eventName | string | 可以观察到的事件有 ‘add’、‘addDir’、‘change’、‘unlink’、‘unlinkDir’、‘ready’、‘error’ 或 ‘all’ |
eventHandler | function | 当指定的事件发生时调用的函数 |
参数 | 类型 | 描述 |
---|---|---|
path | string | 已更改的文件的路径。如果设置了 cwd 选项,则是通过删除 cwd 的相对路径。 |
stats | object | 一个 fs.Stat 对象,但可以是 undefined。如果 alwaysStat 选项被设置为 true,stats 将始终被提供 |
watcher.close()
关闭文件监听器。一旦关闭,就不会再发出任何事件。
watcher.add(globs)
向已经运行的监听器示例添加额外的 globs。
参数 | 类型 | 描述 |
---|---|---|
globs | string array | 额外的要监听的 globs |
watcher.unwatch(globs)
删除正在被监听的 globs,而监视程序继续使用剩余的路径。
参数 | 类型 | 描述 |
---|---|---|
globs | string 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 属性的匿名函数。
由于任何注册任务都可以从命令行运行,避免在任务名称中使用空格。
参数 | 类型 | 描述 |
---|---|---|
taskName | string | 任务系统中任务函数的别名。在为 taskFunction 使用命名函数时不需要。 |
taskFunction(required) | function | 任务函数或组合任务–由 series() 和 parallel() 生成。最好是一个命名函数。可以附加任务元数据,为命令行提供额外信息。 |
返回值
注册任务时,不返回任何内容。
当检索任务时,将返回注册为 taskName 的包装任务(不是原始函数)。包装的任务有一个unwrap() 方法,将返回原始函数。
任务元信息
属性 | 类型 | 描述 |
---|---|---|
name | string | 命名函数的特殊属性。用于注册任务。注意:名称不可写的;它不能被设置或改变 |
displayName | string | 当附加到 taskFunction 时,会为 任务创建一个别名。如果使用函数名称中不允许的字符,请使用此舒心。 |
description | string | 当附加到 taskFunction 时,它提供了在列出任务时由命令函打印的描述。 |
flags | object | 当连接到一个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);