Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gulp.src()内部实现探究 #24

Open
chyingp opened this issue Jul 21, 2014 · 1 comment
Open

gulp.src()内部实现探究 #24

chyingp opened this issue Jul 21, 2014 · 1 comment
Labels

Comments

@chyingp
Copy link
Owner

chyingp commented Jul 21, 2014

写在前面

本来是想写个如何编写gulp插件的科普文的,突然探究欲又发作了,于是就有了这篇东西。。。翻了下源码看了下gulp.src()的实现,不禁由衷感慨:肿么这么复杂。。。

进入正题

首先我们看下gulpfile里面的内容是长什么样子的,很有express中间件的味道是不是~
我们知道.pipe()是典型的流式操作的API。很自然的,我们会想到gulp.src()这个API返回的应该是个Stream对象(也许经过层层封装)。本着一探究竟的目的,花了点时间把gulp的源码大致扫了下,终于找到了答案。

gulpfile.js

var gulp = require('gulp'),
    preprocess = require('gulp-preprocess');

gulp.task('default', function() {

    gulp.src('src/index.html')
        .pipe(preprocess({USERNAME:'程序猿小卡'}))
        .pipe(gulp.dest('dest/'));
});

提前剧透

此处有内容剧透,如有对剧透不适者,请自行跳过本段落。。。

gulp.src() 的确返回了定制化的Stream对象。可以在github上搜索ordered-read-streams这个项目。

大致关系是
ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()

探究之路

首先,我们看下require('gulp')返回了什么。从gulp的源码来看,返回了Gulp对象,该对象上有srcpipedest等方法。很好,找到了我们想要的src方法。接着往下看
参考:https://github.com/gulpjs/gulp/blob/master/index.js#L62

gulp/index.js

var inst = new Gulp();
module.exports = inst;

从下面的代码可以看到,gulp.src方法,实际上是vfs.src。继续
参考:https://github.com/gulpjs/gulp/blob/master/index.js#L25

gulp/index.js

var vfs = require('vinyl-fs');
// 省略很多行代码
Gulp.prototype.src = vfs.src;

接下来我们看下vfs.src这个方法。从vinyl-fs/index.js可以看到,vfs.src实际是vinyl-fs/lib/src/index.js
参考:https://github.com/wearefractal/vinyl-fs/blob/master/index.js

vinyl-fs/index.js

'use strict';

module.exports = {
  src: require('./lib/src'),
  dest: require('./lib/dest'),
  watch: require('glob-watcher')
};

那么,我们看下vinyl-fs/lib/src/index.js。可以看到,gulp.src()返回的,实际是outputStream这货,而outputStreamgs.create(glob, options).pipe()获得的,差不多接近真相了,还有几步而已。
参考:https://github.com/wearefractal/vinyl-fs/blob/master/lib/src/index.js#L37

vinyl-fs/lib/src/index.js

var defaults = require('lodash.defaults');
var through = require('through2');
var gs = require('glob-stream');
var File = require('vinyl');

// 省略非重要代码若干行

function src(glob, opt) {
  // 继续省略代码

  var globStream = gs.create(glob, options);

  // when people write to use just pass it through
  var outputStream = globStream
    .pipe(through.obj(createFile))
    .pipe(getStats(options));

  if (options.read !== false) {
    outputStream = outputStream
      .pipe(getContents(options));
  }
  // 就是这里了
  return outputStream
    .pipe(through.obj());
}

我们再看看glob-stream/index.js里的create方法,最后的return aggregate.pipe(uniqueStream);。好的,下一步就是真相了,我们去ordered-read-streams这个项目一探究竟。
参考:https://github.com/wearefractal/glob-stream/blob/master/index.js#L89

glob-stream/index.js

var through2 = require('through2');
var Combine = require('ordered-read-streams');
var unique = require('unique-stream');

var glob = require('glob');
var minimatch = require('minimatch');
var glob2base = require('glob2base');
var path = require('path');

// 必须省略很多代码

// create 方法
create: function(globs, opt) {
    // 继续省略代码
// create all individual streams
    var streams = positives.map(function(glob){
      return gs.createStream(glob, negatives, opt);
    });

    // then just pipe them to a single unique stream and return it
    var aggregate = new Combine(streams);
    var uniqueStream = unique('path');

    // TODO: set up streaming queue so items come in order

    return aggregate.pipe(uniqueStream);

真相来了,我们看下ordered-read-streams的代码,可能刚开始看不是很懂,没关系,知道它实现了自己的Stream就可以了(nodejs是有暴露相应的API让开发者对Stream进行定制的),具体可参考:http://www.nodejs.org/api/stream.html#stream_api_for_stream_implementors

代码来自:https://github.com/armed/ordered-read-streams/blob/master/index.js

ordered-read-streams/index.js

function OrderedStreams(streams, options) {
  if (!(this instanceof(OrderedStreams))) {
    return new OrderedStreams(streams, options);
  }

  streams = streams || [];
  options = options || {};

  if (!Array.isArray(streams)) {
    streams = [streams];
  }

  options.objectMode = true;

  Readable.call(this, options);

  // stream data buffer
  this._buffs = [];

  if (streams.length === 0) {
    this.push(null); // no streams, close
    return;
  }  

  streams.forEach(function (s, i) {
    if (!s.readable) {
      throw new Error('All input streams must be readable');
    }
    s.on('error', function (e) {
      this.emit('error', e);
    }.bind(this));

    var buff = [];
    this._buffs.push(buff);

    s.on('data', buff.unshift.bind(buff));
    s.on('end', flushStreamAtIndex.bind(this, i));
  }, this);
}

参考:https://github.com/armed/ordered-read-streams/blob/master/index.js

写在后面

兜兜转转一大圈,终于找到了gulp.src()的源头,大致流程如下,算是蛮深的层级。代码细节神马的,有兴趣的同学可以深究一下。

ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()

@chyingp chyingp added the gulp label Jul 21, 2014
@chyingp chyingp changed the title gulp.src()内部实现初探 gulp.src()内部实现探究 Jul 21, 2014
@king-king
Copy link

vinyl-fs对file进行了封装,其最关键的内容contents有三种类型,分别是stream、buffer和null,虽然我没有看过gulp以及vinyl-fs的源代码,但是我认为vinyl-fs最大的功劳应该是将buffer和stream统一封装,让二者虽然类型不同,但是都具备可读流的特性,可以用pipe方法。而pipe可以链式调用,这样一来就能让gulp抛开层层回调的烦恼。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants