hi,你好!欢迎访问本站!登录
本站由网站地图腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

gulp源码剖析

2019-11-18杂谈搜奇网30°c
A+ A-

一.团体组织剖析

团体组织

经由过程在nodejs环境对源码的打印,我们终究取得的gulp实例行如下图。那末我们gulp实例上的属性和要领是怎样生成的呢?

Gulp {
  domain: null,
  _events: [Object: null prototype] {},
  _eventsCount: 0,
  _maxListeners: undefined,
  _registry: DefaultRegistry { _tasks: {} },
  _settle: false,
  watch: [Function: bound ],
  task: [Function: bound task],
  series: [Function: bound series],
  parallel: [Function: bound parallel],
  registry: [Function: bound registry],
  tree: [Function: bound tree],
  lastRun: [Function: bound lastRun],
  src: [Function: bound src],
  dest: [Function: bound dest],
  symlink: [Function: bound symlink] 
}

(1)类的完成

源码index.js分为三个部份,第一部份是gulp组织函数,增加实例要领,第二部份是Gulp原型要领增加,第三部份是导出gulp实例。

//第一部份是gulp组织函数,增加实例要领
function  Gulp(){
  .......
}
//第二部份是原型要领增加 Gulp.prototype.src=....... //第三部份是导出gulp实例 Gulp.prototype.Gulp = Gulp; var inst = new Gulp(); module.exports = inst;

我们晓得完成一个类,经由过程组织函数往this对象增加实例属性和要领,或许增加原型要领,类的实例自动具有实例属性、实例要领和原型要领。

//People类
function People () {
  this.name = "Yorhom";
}

People.prototype.getName = function () {
 console.log(this.name);// "Yorhom"
};

var yorhom = new People();

yorhom.getName()
console.log(yorhom.name)// "Yorhom"

所以我们在组织函数中this对象定义的watch等10个要领细致指的哪些要领呢?实在它包含util.inherits(Gulp, Undertaker); 完成了undertaker原型要领tree()、task()、series()、lastRun()、parallel()、registry()的继续,然后经由过程prototype定义了src()、dest()、symlink()、watch()。末了要说的是 Undertaker.call(this); ,它经由过程Call继续完成了对Undertaker实例属性_registry和_settle 

//https://github.com/gulpjs/undertaker,index.js
function Undertaker(customRegistry) {
  EventEmitter.call(this);

  this._registry = new DefaultRegistry();
  if (customRegistry) {
    this.registry(customRegistry);
  }

  this._settle = (process.env.UNDERTAKER_SETTLE === 'true');
}

inherits(Undertaker, EventEmitter);


Undertaker.prototype.tree = tree;

Undertaker.prototype.task = task;

Undertaker.prototype.series = series;

Undertaker.prototype.lastRun = lastRun;

Undertaker.prototype.parallel = parallel;

Undertaker.prototype.registry = registry;

Undertaker.prototype._getTask = _getTask;

Undertaker.prototype._setTask = _setTask;

要零丁的说一下,call()和bind()。

call用来完成继续。

//MDN文档U
call() 要领运用一个指定的 this 值和零丁给出的一个或多个参数来挪用一个函数。

bind要领建立一个函数,而且函数的this替换成当前Gulp。比方, this.watch = this.watch.bind(this); 该行代码建立了watch要领。

bind()要领建立一个新的函数,在bind()被挪用时,这个新函数的this被bind的第一个参数指定,其他的参数将作为新函数的参数供挪用时运用。

(2)导出实例

导出实例天然是为了运用gulp,我们先来看一个gulpfile.js的代码组织。

var gulp = require('gulp')
.........

gulp.task('uglifyjs', function () {
    var combined = combiner.obj([
        gulp.src('src/js/**/*.js'),
        sourcemaps.init(),
        uglify(),
        sourcemaps.write('./'),
        gulp.dest('dist/js/')
    ])
    combined.on('error', handleError)
})
.........
gulp.task('default', [
    // build
    'uglifyjs',.......
    ]
)

那末我们是经由过程require引入gulp的。我们已完成了一个Gulp的类,new Gulp()取得的实例自动具有继续的属性和要领。但是 Gulp.prototype.Gulp = Gulp; 为何要做这个赋值呢?作用是将组织函数挂载到Gulp原型上,那末实例就能够经由过程Gulp属性接见到组织函数了。

Gulp.prototype.Gulp = Gulp;

var inst = new Gulp();
//console.log(inst)
module.exports = inst;

 

二.接口gulp.src()和gulp.dest()

gulp的src和dest接口直接引用了vinyl-fs模块https://github.com/gulpjs/vinyl-fs。

(1)vinyl是什么?

起首,vinyl-fs是针对文件体系的vinyl 适配器。

那末vinyl是什么呢?

What is Vinyl?
Vinyl is a very simple metadata object that describes a file. 
When you think of a file, two attributes come to mind: path and contents. These are the main attributes on a Vinyl object. 
A file does not necessarily represent something on your computer’s file system. You have files on S3, FTP, Dropbox, Box, CloudThingly.io and other services.
 Vinyl can be used to describe files from all of these sources.

vinyl是vitual  file formate假造文件花样,用于形貌一个文件。它有两个重要的属性,path属性和contents属性。每个Vinyl实例代表一个自力的文件、目次或许symlink标记衔接。

现实项目中,除了我们须要一个简约的体式格局去形貌一个文件以外,我们还须要接见这些文件,所以vinyl适配器对外暴露接口src()和dest(),他们都终究返回一个流(stream),src接口生产一个Vinyl 对象,dest接口消耗一个Vinyl 对象。

(2)stream流的工作体式格局

流(stream)是node.js处置惩罚流式数据的笼统接口。流是可读可写的,一切的流都是EventEmitter实例。

#流的范例#

Node.js 中有四种基础的流范例:

Writable - 可写入数据的流(比方 fs.createWriteStream())。
Readable - 可读取数据的流(比方 fs.createReadStream())。
Duplex - 可读又可写的流(比方 net.Socket)。
Transform - 在读写过程当中能够修正或转换数据的 Duplex 流(比方 zlib.createDeflate())。
另外,该模块还包含有用函数 stream.pipeline()、stream.finished() 和 stream.Readable.from()。

流有缓冲(停息)机制。

可写流和可读流都邑在内部的缓冲器中存储数据。

当挪用 stream.push(chunk) 时,数据会被缓冲在可读流中。 假如流的消耗者没有挪用 stream.read(),则数据会保存在内部行列中直到被消耗。一旦内部的可读缓冲的总大小到达 highWaterMark 指定的阈值时,流会临时住手从底层资本读取数据,直到当前缓冲的数据被消耗。

当挪用 writable.write(chunk) 时,数据会被缓冲在可写流中。 当内部的可写缓冲的总大小小于 highWaterMark 设置的阈值时,挪用 writable.write() 会返回 true。 一旦内部缓冲的大小到达或凌驾 highWaterMark 时,则会返回 false

stream API 的重要目的,特别是 stream.pipe(),是为了限定数据的缓冲到可接受的水平,也就是读写速率不一致的泉源与目的地不会压垮内存。

可写流

可写流是对数据要被写入的目的地的一种笼统。

可写流的例子包含:

客户端的 HTTP 要求
服务器的 HTTP 相应
fs 的写入流
zlib 流
crypto 流
TCP socket
子历程 stdin
process.stdout、process.stderr

当在可读流上挪用 stream.pipe() 要领时会发出 'pipe' 事宜,并将此可写流增加到其目的集。

const writer = getWritableStreamSomehow();
const reader = getReadableStreamSomehow();
writer.on('pipe', (src) => {
  console.log('有数据正经由过程管道流入写入器');
  assert.equal(src, reader);
});
reader.pipe(writer);

可读流

可读流是对供应数据的泉源的一种笼统。

可读流的例子包含:

客户端的 HTTP 相应
服务器的 HTTP 要求
fs 的读取流
zlib 流
crypto 流
TCP socket
子历程 stdout 与 stderr
process.stdin
一切可读流都完成了 stream.Readable 类定义的接口。

关于大多数用户,发起运用 readable.pipe(),由于它是消耗流数据最简朴的体式格局。假如开发者须要精细地掌握数据的通报与发作,能够运用 EventEmitter、 readable.on('readable')/readable.read() 或 readable.pause()/readable.resume()

(3)src接口代码组织以及读取完成

function src(glob, opt) {
  var optResolver = createResolver(config, opt);

  if (!isValidGlob(glob)) {
    throw new Error('Invalid glob argument: ' + glob);
  }

  var streams = [      
gs(glob, opt).... readContents(optResolver), .... ];

var outputStream = pumpify.obj(streams); return toThrough(outputStream); } module.exports = src;

其他关于读取的功用增加我们本篇文章就不细致说了,只是集合在读取内容的中心代码上。

起首,建立createResolver对默许设置和传入的options(吸收哪些选项,请参照https://www.gulpjs.com.cn/docs/api/src/)建立了一个resolver。然后当我们经由过程 resolver.resolve(optionKey, [...arguments]) 就能够够剖析相干选项了。

//  libs/src/options.js 默许选项
var config = {
  buffer: {
    type: 'boolean',
    default: true,
  },
  read: {
    type: 'boolean',
    default: true,
  },
  since: {
    type: 'date',
  },
  removeBOM: {
    type: 'boolean',
    default: true,
  },
  sourcemaps: {
    type: 'boolean',
    default: false,
  },
  resolveSymlinks: {
    type: 'boolean',
    default: true,
  },
};

module.exports = config;

然后,glob流读取 gs(glob, opt), 

//glob-stream运用示例
var gs = require('glob-stream');
 
var readable = gs('./files/**/*.coffee', { /* options */ });
 
var writable = /* your WriteableStream */
 
readable.pipe(writable);

接下来,取得输出流 var outputStream = pumpify.obj(streams); pumpify的功用是组装一个流的数组成为一个Duplex流(这个在Nodejs中是可读可写的流)。假如在管道中个中一个流被关掉或许发作毛病,那末一切的流都邑被烧毁。

pumpify
Combine an array of streams into a single duplex stream using pump and duplexify. If one of the streams closes/errors all streams in the pipeline will be destroyed.

末了, return toThrough(outputStream); toThrough包装输出流为一个Transform流。Transform 流是在读写过程当中能够修正或转换数据的 Duplex 流

to-through,Wrap a ReadableStream in a TransformStream.

所以我们看到,运用src接口取得的是一个Transform流。

(3)dest接口代码组织以及写入完成

function dest(outFolder, opt) {

  var optResolver = createResolver(config, opt);
  var folderResolver = createResolver(folderConfig, { outFolder: outFolder });

  function dirpath(file, callback) {
    var dirMode = optResolver.resolve('dirMode', file);

    callback(null, file.dirname, dirMode);
  }

  var saveStream = pumpify.obj(
    。。。
    mkdirpStream.obj(dirpath)
    。。。
    writeContents(optResolver)
  );

  // Sink the output stream to start flowing
  return lead(saveStream);
}

module.exports = dest;

  mkdirpStream.obj(dirpath) 功用是确保目次存在。

fs-mkdirp-stream
//确保在写入这个目次之前它是存在的

Ensure directories exist before writing to them.

末了写入 return lead(saveStream); 

//lead要领的作用
Takes a stream to sink and returns the same stream. 
Sets up event listeners to infer if the stream is being used as a Transform 
or Writeable stream and sinks it on nextTick if necessary. 
If the stream is being used as a Transform stream but becomes unpiped,
 it will be sunk. Respects pipe, on('data') and on('readable') handlers.

 

三.接口gulp.task()

gulp中的task是继续了undertaker中的task要领。undertaker有关task的源码有个部份。

(1)lib/task.js

//task运用
//第一种,传入函数
const { task } = require('gulp');

function build(cb) {
  // body omitted
  cb();
}

task(build);
//第二种,第一个参数为字符串,第二个参数为function
task('build', function(cb) {
  // body omitted
  cb();
});

所以task.js对参数进行了推断。

function task(name, fn) {
  //这是针对第一种直接传入函数的参数转化,转化后name为使命名,fn为函数体,与第二种体式格局就一致了
  if (typeof name === 'function') {
    fn = name;
    name = fn.displayName || fn.name;
  }
//若没有第二个参数,那末就是猎取task
  if (!fn) {
    return this._getTask(name);
  }
//第二个参数存在,那末就是设置task
  this._setTask(name, fn);
}

(2)lib/get-task.js、lib/set-task.js

//挪用的是_registry的要领
function get(name) {
  return this._registry.get(name);
}
function set(name, fn) {
 //参数推断
  assert(name, 'Task name must be specified');
  assert(typeof name === 'string', 'Task name must be a string');
  assert(typeof fn === 'function', 'Task function must be specified');
 //undertakder继续task自定义的函数
  function taskWrapper() {
    return fn.apply(this, arguments);
  }
  //return  task函数体
  function unwrap() {
    return fn;
  }

  taskWrapper.unwrap = unwrap;
  taskWrapper.displayName = name;
  
  //metadata是一个weak map的实例
  var meta = metadata.get(fn) || {};
  var nodes = [];
  if (meta.branch) {
    nodes.push(meta.tree);
  }
  //建立task
  var task = this._registry.set(name, taskWrapper) || taskWrapper;
 //metadata设置task map数据。
  metadata.set(task, {
    name: name,
    orig: fn,
    tree: {
      label: name,
      type: 'task',
      nodes: nodes,
    },
  });
}

module.exports = set;

 lib/registry.js

function setTasks(inst, task, name) {
  inst.set(name, task);
  return inst;
}

function registry(newRegistry) {
  if (!newRegistry) {
    return this._registry;
  }

  validateRegistry(newRegistry);
  //

  var tasks = this._registry.tasks();
//什么是object.reduce
//Reduces an object to a value that is the accumulated result of running each property in the object through a callback.
//第一个参数是遍历的对象,第二个参数是每次迭代时运转的函数,第三个参数是初始化的value值
  this._registry = reduce(tasks, setTasks, newRegistry);
  this._registry.init(this);
}

module.exports = registry;

它对tasks数组迭代,每次迭代运转一次setTask要领。

 

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
gulp源码剖析

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282083.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>