gulp学习(2)

Posted on By yuanfang

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);
}
//略...