文章出處

寫在前面

本來是想寫個如何編寫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()


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()