一.整体结构分析

整体结构

通过在nodejs环境对源码的打印,我们最终得到的gulp实例行如下图。那么我们gulp实例上的属性和方法是如何生成的呢?

  1. Gulp {
  2. domain: null,
  3. _events: [Object: null prototype] {},
  4. _eventsCount: 0,
  5. _maxListeners: undefined,
  6. _registry: DefaultRegistry { _tasks: {} },
  7. _settle: false,
  8. watch: [Function: bound ],
  9. task: [Function: bound task],
  10. series: [Function: bound series],
  11. parallel: [Function: bound parallel],
  12. registry: [Function: bound registry],
  13. tree: [Function: bound tree],
  14. lastRun: [Function: bound lastRun],
  15. src: [Function: bound src],
  16. dest: [Function: bound dest],
  17. symlink: [Function: bound symlink]
  18. }

(1)类的实现

源码index.js分为三个部分,第一部分是gulp构造函数,添加实例方法,第二部分是Gulp原型方法添加,第三部分是导出gulp实例。

  1. //第一部分是gulp构造函数,添加实例方法
  2. function Gulp(){
  3. .......
  4. }
  5. //第二部分是原型方法添加
  6. Gulp.prototype.src=.......
  7.  
  8. //第三部分是导出gulp实例
  9. Gulp.prototype.Gulp = Gulp;
  10.  
  11. var inst = new Gulp();
  12. module.exports = inst;

我们知道实现一个类,通过构造函数往this对象添加实例属性和方法,或者添加原型方法,类的实例自动拥有实例属性、实例方法和原型方法。

  1. //People类
  2. function People () {
  3. this.name = "Yorhom";
  4. }
  5.  
  6. People.prototype.getName = function () {
  7. console.log(this.name);// "Yorhom"
  8. };
  9.  
  10. var yorhom = new People();
  11.  
  12. yorhom.getName()
  13. 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

  1. //https://github.com/gulpjs/undertaker,index.js
  2. function Undertaker(customRegistry) {
  3. EventEmitter.call(this);
  4.  
  5. this._registry = new DefaultRegistry();
  6. if (customRegistry) {
  7. this.registry(customRegistry);
  8. }
  9.  
  10. this._settle = (process.env.UNDERTAKER_SETTLE === 'true');
  11. }
  12.  
  13. inherits(Undertaker, EventEmitter);
  14.  
  15. Undertaker.prototype.tree = tree;
  16.  
  17. Undertaker.prototype.task = task;
  18.  
  19. Undertaker.prototype.series = series;
  20.  
  21. Undertaker.prototype.lastRun = lastRun;
  22.  
  23. Undertaker.prototype.parallel = parallel;
  24.  
  25. Undertaker.prototype.registry = registry;
  26.  
  27. Undertaker.prototype._getTask = _getTask;
  28.  
  29. Undertaker.prototype._setTask = _setTask;

要单独的说一下,call()和bind()。

call用来实现继承。

  1. //MDN文档U
  2. call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

bind方法创建一个函数,而且函数的this替换成当前Gulp。例如, this.watch = this.watch.bind(this); 该行代码创建了watch方法。

  1. bind()方法创建一个新的函数,在bind()被调用时,这个新函数的thisbind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

(2)导出实例

导出实例自然是为了使用gulp,我们先来看一个gulpfile.js的代码结构。

  1. var gulp = require('gulp')
  2. .........
  3.  
  4. gulp.task('uglifyjs', function () {
  5. var combined = combiner.obj([
  6. gulp.src('src/js/**/*.js'),
  7. sourcemaps.init(),
  8. uglify(),
  9. sourcemaps.write('./'),
  10. gulp.dest('dist/js/')
  11. ])
  12. combined.on('error', handleError)
  13. })
  14. .........
  15. gulp.task('default', [
  16. // build
  17. 'uglifyjs',.......
  18. ]
  19. )

那么我们是通过require引入gulp的。我们已经实现了一个Gulp的类,new Gulp()获得的实例自动拥有继承的属性和方法。可是 Gulp.prototype.Gulp = Gulp; 为什么要做这个赋值呢?作用是将构造函数挂载到Gulp原型上,那么实例就能通过Gulp属性访问到构造函数了。

  1. Gulp.prototype.Gulp = Gulp;
  2.  
  3. var inst = new Gulp();
  4. //console.log(inst)
  5. module.exports = inst;

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

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

(1)vinyl是什么?

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

那么vinyl是什么呢?

  1. What is Vinyl?
  2. Vinyl is a very simple metadata object that describes a file.
  3. When you think of a file, two attributes come to mind: path and contents. These are the main attributes on a Vinyl object.
  4. A file does not necessarily represent something on your computers file system. You have files on S3, FTP, Dropbox, Box, CloudThingly.io and other services.
  5. 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实例。

  1. #流的类型#
  2.  
  3. Node.js 中有四种基本的流类型:
  4.  
  5. Writable - 可写入数据的流(例如 fs.createWriteStream())。
  6. Readable - 可读取数据的流(例如 fs.createReadStream())。
  7. Duplex - 可读又可写的流(例如 net.Socket)。
  8. Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。
  9. 此外,该模块还包括实用函数 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(),是为了限制数据的缓冲到可接受的程度,也就是读写速度不一致的源头与目的地不会压垮内存。

可写流

  1. 可写流是对数据要被写入的目的地的一种抽象。
  2.  
  3. 可写流的例子包括:
  4.  
  5. 客户端的 HTTP 请求
  6. 服务器的 HTTP 响应
  7. fs 的写入流
  8. zlib
  9. crypto
  10. TCP socket
  11. 子进程 stdin
  12. process.stdoutprocess.stderr

当在可读流上调用 stream.pipe() 方法时会发出 'pipe' 事件,并将此可写流添加到其目标集。

  1. const writer = getWritableStreamSomehow();
  2. const reader = getReadableStreamSomehow();
  3. writer.on('pipe', (src) => {
  4. console.log('有数据正通过管道流入写入器');
  5. assert.equal(src, reader);
  6. });
  7. reader.pipe(writer);

可读流

  1. 可读流是对提供数据的来源的一种抽象。
  2.  
  3. 可读流的例子包括:
  4.  
  5. 客户端的 HTTP 响应
  6. 服务器的 HTTP 请求
  7. fs 的读取流
  8. zlib
  9. crypto
  10. TCP socket
  11. 子进程 stdout stderr
  12. process.stdin
  13. 所有可读流都实现了 stream.Readable 类定义的接口。

对于大多数用户,建议使用 readable.pipe(),因为它是消费流数据最简单的方式。如果开发者需要精细地控制数据的传递与产生,可以使用 EventEmitter readable.on('readable')/readable.read() 或 readable.pause()/readable.resume()

(3)src接口代码结构以及读取实现

  1. function src(glob, opt) {
  2. var optResolver = createResolver(config, opt);
  3.  
  4. if (!isValidGlob(glob)) {
  5. throw new Error('Invalid glob argument: ' + glob);
  6. }
  7.  
  8. var streams = [
    gs(glob, opt).... readContents(optResolver), .... ];
  9. 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]) 就可以解析相关选项了。

  1. // libs/src/options.js 默认选项
  2. var config = {
  3. buffer: {
  4. type: 'boolean',
  5. default: true,
  6. },
  7. read: {
  8. type: 'boolean',
  9. default: true,
  10. },
  11. since: {
  12. type: 'date',
  13. },
  14. removeBOM: {
  15. type: 'boolean',
  16. default: true,
  17. },
  18. sourcemaps: {
  19. type: 'boolean',
  20. default: false,
  21. },
  22. resolveSymlinks: {
  23. type: 'boolean',
  24. default: true,
  25. },
  26. };
  27.  
  28. module.exports = config;

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

  1. //glob-stream使用示例
  2. var gs = require('glob-stream');
  3.  
  4. var readable = gs('./files/**/*.coffee', { /* options */ });
  5.  
  6. var writable = /* your WriteableStream */
  7.  
  8. readable.pipe(writable);

接下来,获得输出流 var outputStream = pumpify.obj(streams); pumpify的功能是组装一个流的数组成为一个Duplex流(这个在Nodejs中是可读可写的流)。如果在管道中其中一个流被关掉或者发生错误,那么所有的流都会被销毁。

  1. pumpify
  2. 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 流

  1. to-throughWrap a ReadableStream in a TransformStream.

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

(3)dest接口代码结构以及写入实现

  1. function dest(outFolder, opt) {
  2.  
  3. var optResolver = createResolver(config, opt);
  4. var folderResolver = createResolver(folderConfig, { outFolder: outFolder });
  5.  
  6. function dirpath(file, callback) {
  7. var dirMode = optResolver.resolve('dirMode', file);
  8.  
  9. callback(null, file.dirname, dirMode);
  10. }
  11.  
  12. var saveStream = pumpify.obj(
  13. 。。。
  14. mkdirpStream.obj(dirpath)
  15. 。。。
  16. writeContents(optResolver)
  17. );
  18.  
  19. // Sink the output stream to start flowing
  20. return lead(saveStream);
  21. }
  22.  
  23. module.exports = dest;

mkdirpStream.obj(dirpath) 功能是确保目录存在。

  1. fs-mkdirp-stream
  2. //确保在写入这个目录之前它是存在的
  3.  
  4. Ensure directories exist before writing to them.

最后写入 return lead(saveStream);

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

三.接口gulp.task()

gulp中的task是继承了undertaker中的task方法。undertaker有关task的源码有个部分。

(1)lib/task.js

  1. //task使用
  2. //第一种,传入函数
  3. const { task } = require('gulp');
  4.  
  5. function build(cb) {
  6. // body omitted
  7. cb();
  8. }
  9.  
  10. task(build);
  11. //第二种,第一个参数为字符串,第二个参数为function
  12. task('build', function(cb) {
  13. // body omitted
  14. cb();
  15. });

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

  1. function task(name, fn) {
  2. //这是针对第一种直接传入函数的参数转化,转化后name为任务名,fn为函数体,与第二种方式就一致了
  3. if (typeof name === 'function') {
  4. fn = name;
  5. name = fn.displayName || fn.name;
  6. }
  7. //若没有第二个参数,那么就是获取task
  8. if (!fn) {
  9. return this._getTask(name);
  10. }
  11. //第二个参数存在,那么就是设置task
  12. this._setTask(name, fn);
  13. }

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

  1. //调用的是_registry的方法
  2. function get(name) {
  3. return this._registry.get(name);
  4. }
  1. function set(name, fn) {
  2. //参数判断
  3. assert(name, 'Task name must be specified');
  4. assert(typeof name === 'string', 'Task name must be a string');
  5. assert(typeof fn === 'function', 'Task function must be specified');
  6. //undertakder继承task自定义的函数
  7. function taskWrapper() {
  8. return fn.apply(this, arguments);
  9. }
  10. //return task函数体
  11. function unwrap() {
  12. return fn;
  13. }
  14.  
  15. taskWrapper.unwrap = unwrap;
  16. taskWrapper.displayName = name;
  17.  
  18. //metadata是一个weak map的实例
  19. var meta = metadata.get(fn) || {};
  20. var nodes = [];
  21. if (meta.branch) {
  22. nodes.push(meta.tree);
  23. }
  24. //创建task
  25. var task = this._registry.set(name, taskWrapper) || taskWrapper;
  26. //metadata设置task map数据。
  27. metadata.set(task, {
  28. name: name,
  29. orig: fn,
  30. tree: {
  31. label: name,
  32. type: 'task',
  33. nodes: nodes,
  34. },
  35. });
  36. }
  37.  
  38. module.exports = set;

lib/registry.js

  1. function setTasks(inst, task, name) {
  2. inst.set(name, task);
  3. return inst;
  4. }
  5.  
  6. function registry(newRegistry) {
  7. if (!newRegistry) {
  8. return this._registry;
  9. }
  10.  
  11. validateRegistry(newRegistry);
  12. //
  13.  
  14. var tasks = this._registry.tasks();
  15. //什么是object.reduce
  16. //Reduces an object to a value that is the accumulated result of running each property in the object through a callback.
  17. //第一个参数是遍历的对象,第二个参数是每次迭代时运行的函数,第三个参数是初始化的value值
  18. this._registry = reduce(tasks, setTasks, newRegistry);
  19. this._registry.init(this);
  20. }
  21.  
  22. module.exports = registry;

它对tasks数组迭代,每次迭代运行一次setTask方法。

gulp源码分析的更多相关文章

  1. 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 百篇博客分析OpenHarmony源码 | v59.01

    百篇博客系列篇.本篇为: v59.xx 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿 ...

  2. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  7. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

随机推荐

  1. 虚拟机VMware14 pro下安装REHL5U11

    1. 创建虚拟磁盘,自定义,磁盘类型选IDE,确保安装系统过程中只有一个物理光盘驱动/ISO镜像: 2. 安装VMware Tools 2.1 虚拟机>安装VMware Tools 2.2 在光 ...

  2. Python学习笔记整理总结【Django】【MVC/MTV/路由分配系统(URL)/视图函数 (views)/表单交互】

     一.Web框架概述  Web框架本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python # -*- coding:utf-8 ...

  3. maven 打包构建相关命令

    1.命令 mvn clean package 依次执行clean.resources.compile.testResources.testCompile.test.jar(打包)等7个阶段. mvn ...

  4. java8 Optional使用总结

    [前言] java8新特性 java8 函数接口 java8 lambda表达式 Java 8 时间日期使用 java8 推出的Optional的目的就是为了杜绝空指针异常,帮助开发者开发出更优雅的代 ...

  5. 通过父级id获取到其下所有子级(无穷级)——Mysql函数实现

    [需求]某用户只能查看其自己信息及其下级信息,涉及通过该用户所在部门获取其下所有部门(多层)id集合. 步骤一:对数据库进行设置: set global log_bin_trust_function_ ...

  6. 序列标注(HMM/CRF)

    目录 简介 隐马尔可夫模型(HMM) 条件随机场(CRF) 马尔可夫随机场 条件随机场 条件随机场的特征函数 CRF与HMM的对比 维特比算法(Viterbi) 简介 序列标注(Sequence Ta ...

  7. eShopOnContainers学习系列(三):RabbitMQ消息总线实践

    今天研究了下eShopOnContainers里的RabbitMQ的使用,在项目里是以封装成消息总线的方式使用的,但是仍然是以其发布.订阅两个方法作为基础封装的,我们今天就来实际使用一下. 为了简单起 ...

  8. ELK 学习笔记之 elasticsearch bool组合查询

    elasticsearch bool组合查询: 相当于sql:where _type = 'books' and (price = 500 or title = 'bigdata') Note: mu ...

  9. Rust入坑指南:常规套路

    搭建好了开发环境之后,就算是正式跳进Rust的坑了,今天我就要开始继续向下挖了. 由于我们初来乍到 ,对Rust还不熟悉,所以我决定先走一遍常规套路. 变不变的变量 学习一门语言第一个要了解的当然就是 ...

  10. 小游戏:200行python代码手写2048

    #-*- coding: utf-8 -*- import curses from random import randrange, choice from collections import de ...