gulp源码解析(二)—— vinyl-fs
注意attention: 转载学习来源https://www.cnblogs.com/vajoy/p/6357476.html
为了分析源码,我们打开 gulp 仓库下的入口文件 index.js,可以很直观地发现,几个主要的 API 都是直接引用 vinyl-fs 模块上暴露的接口的:
var util = require('util');
var Undertaker = require('undertaker');
var vfs = require('vinyl-fs');
var watch = require('glob-watcher');
//略...
Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;
Gulp.prototype.symlink = vfs.symlink;
//略...
Vinyl 可以看做一个文件描述器,通过它可以轻松构建单个文件的元数据(metadata object)描述对象。简而言之,Vinyl 可以创建一个文件描述对象,通过接口可以取得该文件所对应的数据(Buffer类型)、cwd路径、文件名等等
//ch2-demo2
var Vinyl = require('vinyl');
var file = new Vinyl({
cwd: '/',
base: '/test/',
path: '/test/newFile.txt',
contents: new Buffer('abc')
});
console.log(file.contents.toString());
console.log('path is: ' + file.path);
console.log('basename is: ' + file.basename);
console.log('filename without suffix: ' + file.stem);
console.log('file extname is: ' + file.extname);
Vinyl-fs 的 .src 接口可以匹配一个通配符,将匹配到的文件转为 Vinyl Stream,而 .dest 接口又能消费这个 Stream,并生成对应文件。
var map = require('map-stream');
var fs = require('vinyl-fs');
var log = function(file, cb) {
console.log(file.path);
cb(null, file);
};
fs.src(['./js/**/*.js', '!./js/vendor/*.js'])
.pipe(map(log))
.pipe(fs.dest('./output'));
src函数
var gs = require('glob-stream');
var pumpify = require('pumpify');
var toThrough = require('to-through');
var isValidGlob = require('is-valid-glob');
var createResolver = require('resolve-options');
var config = require('./options');
var prepare = require('./prepare');
var wrapVinyl = require('./wrap-vinyl');
var sourcemap = require('./sourcemap');
var readContents = require('./read-contents');
var resolveSymlinks = require('./resolve-symlinks');
function src(glob, opt) {
var optResolver = createResolver(config, opt);
if (!isValidGlob(glob)) {
throw new Error('Invalid glob argument: ' + glob);
}
var streams = [
gs(glob, opt),
wrapVinyl(optResolver), // 封装内容为 vinyl file格式
resolveSymlinks(optResolver),
prepare(optResolver),
readContents(optResolver),
sourcemap(optResolver),
];
var outputStream = pumpify.obj(streams);
return toThrough(outputStream);
}
module.exports = src;
在 vinyl-fs 中,是使用 glob-stream 通过算法(minimatch)来解析 GLOB 的,它会拿符合上述 GLOB 模式规范的 pattern 参数去匹配相应的文件; 而 glob-stream 又是借助了 node-glob 来匹配文件列表的;
gs(glob, opt)
var Combine = require('ordered-read-streams');
var unique = require('unique-stream');
var pumpify = require('pumpify');
var isNegatedGlob = require('is-negated-glob');
var extend = require('extend');
var GlobStream = require('./readable');
function globStream(globs, opt) {
if (!opt) {
opt = {};
}
var ourOpt = extend({}, opt);
var ignore = ourOpt.ignore; // 用于glob的参数
//略...
var positives = [];
var negatives = [];
globs.forEach(sortGlobs);
function sortGlobs(globString, index) {
if (typeof globString !== 'string') {
throw new Error('Invalid glob at index ' + index);
}
var glob = isNegatedGlob(globString);
var globArray = glob.negated ? negatives : positives;
globArray.push({
index: index,
glob: glob.pattern,
});
}
if (positives.length === 0) {
throw new Error('Missing positive glob');
}
// Create all individual streams
var streams = positives.map(streamFromPositive);
// 这里使用了 ordered-read-streams 模块将一个数组的 Streams 合并为单个 Stream
var aggregate = new Combine(streams);
//对合成的 Stream 进行去重处理(以“path”属性为指标)
var uniqueStream = unique(ourOpt.uniqueBy);
return pumpify.obj(aggregate, uniqueStream);
function streamFromPositive(positive) {
var negativeGlobs = negatives
.filter(indexGreaterThan(positive.index))
.map(toGlob)
.concat(ignore);
return new GlobStream(positive.glob, negativeGlobs, ourOpt);
}
}
function indexGreaterThan(index) {
return function(obj) {
return obj.index > index;
};
}
function toGlob(obj) {
return obj.glob;
}
new GlobStream
function GlobStream(ourGlob, negatives, opt) {
if (!(this instanceof GlobStream)) {
return new GlobStream(ourGlob, negatives, opt);
}
var ourOpt = extend({}, opt);
Readable.call(this, {
objectMode: true,
highWaterMark: ourOpt.highWaterMark || 16,
});
// Delete `highWaterMark` after inheriting from Readable
delete ourOpt.highWaterMark;
var self = this;
function resolveNegatives(negative) {
return toAbsoluteGlob(negative, ourOpt);
}
var ourNegatives = negatives.map(resolveNegatives);
ourOpt.ignore = ourNegatives;
var cwd = ourOpt.cwd;
var allowEmpty = ourOpt.allowEmpty || false;
// Extract base path from glob
var basePath = ourOpt.base || getBasePath(ourGlob, ourOpt);
// Remove path relativity to make globs make sense
ourGlob = toAbsoluteGlob(ourGlob, ourOpt);
// Delete `root` after all resolving done
delete ourOpt.root;
var globber = new glob.Glob(ourGlob, ourOpt);
this._globber = globber;
var found = false;
globber.on('match', function(filepath) {
found = true;
var obj = {
cwd: cwd,
base: basePath,
path: removeTrailingSeparator(filepath),
};
if (!self.push(obj)) {
globber.pause();
}
});
globber.once('end', function() {
if (allowEmpty !== true && !found && globIsSingular(globber)) {
var err = new Error(globErrMessage1 + ourGlob + globErrMessage2);
return self.destroy(err);
}
self.push(null);
});
function onError(err) {
self.destroy(err);
}
globber.once('error', onError);
}
inherits(GlobStream, Readable);
GlobStream.prototype._read = function() {
this._globber.resume();
};
GlobStream.prototype.destroy = function(err) {
var self = this;
this._globber.abort();
process.nextTick(function() {
if (err) {
self.emit('error', err);
}
self.emit('close');
});
};
readable-stream self.push
node中的Stream-Readable和Writeable解读
pumpify.obj
//略...下面是pump的index源码里面的
// 利用pipe进行多个流的合并
var pipe = function (from, to) {
return from.pipe(to)
}
var pump = function () {
var streams = Array.prototype.slice.call(arguments)
var callback = isFn(streams[streams.length - 1] || noop) && streams.pop() || noop
if (Array.isArray(streams[0])) streams = streams[0]
if (streams.length < 2) throw new Error('pump requires two streams per minimum')
var error
var destroys = streams.map(function (stream, i) {
var reading = i < streams.length - 1
var writing = i > 0
return destroyer(stream, reading, writing, function (err) {
if (!error) error = err
if (err) destroys.forEach(call)
if (reading) return
destroys.forEach(call)
callback(error)
})
})
streams.reduce(pipe)
}
//略...
wrapVinyl(optResolver)
var File = require('vinyl');
var through = require('through2');
function wrapVinyl() {
function wrapFile(globFile, enc, callback) {
var file = new File(globFile);
callback(null, file);
}
return through.obj(wrapFile);
}
readContents(optResolver)
//略...
// 设置file.content
function readContents(optResolver) {
function readFile(file, enc, callback) {
// Skip reading contents if read option says so
var read = optResolver.resolve('read', file);
if (!read) {
return callback(null, file);
}
// Don't fail to read a directory
if (file.isDirectory()) {
return readDir(file, optResolver, onRead);
}
// Process symbolic links included with `resolveSymlinks` option
if (file.stat && file.stat.isSymbolicLink()) {
return readSymbolicLink(file, optResolver, onRead);
}
// Read and pass full contents
var buffer = optResolver.resolve('buffer', file);
if (buffer) {
return readBuffer(file, optResolver, onRead);
}
// Don't buffer anything - just pass streams
return readStream(file, optResolver, onRead);
// This is invoked by the various readXxx modules when they've finished
// reading the contents.
function onRead(readErr) {
if (readErr) {
return callback(readErr);
}
return callback(null, file);
}
}
return through.obj(readFile);
}
//略...
desc函数
var through = require('through2');
var writeDir = require('./write-dir');
var writeStream = require('./write-stream');
var writeBuffer = require('./write-buffer');
var writeSymbolicLink = require('./write-symbolic-link');
var fo = require('../../file-operations');
function writeContents(optResolver) {
function writeFile(file, enc, callback) {
// Write it as a symlink
if (file.isSymbolic()) {
return writeSymbolicLink(file, optResolver, onWritten);
}
// If directory then mkdirp it
if (file.isDirectory()) {
return writeDir(file, optResolver, onWritten);
}
// Stream it to disk yo
if (file.isStream()) {
return writeStream(file, optResolver, onWritten);
}
// Write it like normal
if (file.isBuffer()) {
return writeBuffer(file, optResolver, onWritten);
}
// If no contents then do nothing
if (file.isNull()) {
return onWritten();
}
// This is invoked by the various writeXxx modules when they've finished
// writing the contents.
function onWritten(writeErr) {
var flags = fo.getFlags({
overwrite: optResolver.resolve('overwrite', file),
append: optResolver.resolve('append', file),
});
if (fo.isFatalOverwriteError(writeErr, flags)) {
return callback(writeErr);
}
callback(null, file);
}
}
return through.obj(writeFile);
}
//略...