在cachedInput、output、watch三大文件系统中,output非常简单,没有必要讲,其余两个模块依赖于input模块,而input主要是引用了graceful-fs的部分API,所以这节来讲讲graceful-fs。

  上一节整理的源码如下:

var fs = require('fs')// ...工具方法

module.exports = patch(require('./fs.js'))
if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) {module.exports = patch(fs)
}module.exports.close = fs.close = (function(fs$close) { /*...*/ })(fs.close)module.exports.closeSync = fs.closeSync = (function(fs$closeSync) { /*...*/ })(fs.closeSync)function patch(fs) {// fs方法二次封装return fs
}

  内容包含:

1、工具方法

2、patch引入的fs模块并输出

3、添加close/closeSync方法

util.debuglog

  首先看工具方法,代码如下:

var util = require('util');// 检测此方法是否存在并返回一个debug方法
if (util.debuglog)debug = util.debuglog('gfs4');
// 测试进程参数NODE_DEBUG是否包含'gfs4'
else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {//  自定义一个debug函数debug = (...args) => {var m = util.format.apply(util, args);m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ');console.error(m);}
}if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {// 监听退出事件process.on('exit', function() {// 批量输出日志内容
        debug(queue);// 使用==测试参数是否相等 不等抛出errorrequire('assert').equal(queue.length, 0);})
}

  这里会尝试调用util.debuglog来生成一个错误日志函数,每一次调用该函数会打印一条错误日志。

  在没有util.debuglog的情况下后自定义一个debug函数,测试代码如图:

const util = require('util');
debug = (...args) => {var m = util.format.apply(util, args);m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ');console.error(m);
}
debug(`log1
log2
log3`);

  执行后输出如图:

  这里可以顺便看一下nodejs中debuglog的源码,整理如下:

var debugs = {};
// 收集所有DEBUG的环境名
var debugEnviron;function debuglog(set) {if (debugEnviron === undefined) {// 从NODE_DEBUG环境变量中收集所有的日志输出参数// 这里全部转为大写// 这就说明为什么debuglog传的是gfs4 输出的是GFS4debugEnviron = new Set((process.env.NODE_DEBUG || '').split(',').map((s) => s.toUpperCase()));}set = set.toUpperCase();// 没有该debuglog函数就创建一个if (!debugs[set]) {// 只对指定的参数进行输出if (debugEnviron.has(set)) {var pid = process.pid;debugs[set] = function() {// 格式化参数信息var msg = exports.format.apply(exports, arguments);// 依次输出:参数名 进程号 信息console.error('%s %d: %s', set, pid, msg);};} else {debugs[set] = function() {};}}return debugs[set];
}

  可以看到,源码内部也是用console.error来进行错误日志输出,输出的格式比模拟方法多了一个进程号,基本上没啥区别。

  官网的实例我测不出来,先搁着,下面讲模块输出。

 模块输出'./fs.js'

  模块的输出有两个方式,取决的系统环境信息 TEST_GRACEFUL_FS_GLOBAL_PATCH ,这个参数可以设置,默认是undefined。

  若该值未设置,会调用本地的fs来进行patch,这个本地fs源码如下:

'use strict'var fs = require('fs')module.exports = clone(fs)// 拷贝对象
function clone(obj) {if (obj === null || typeof obj !== 'object')return objif (obj instanceof Object)var copy = { __proto__: obj.__proto__ }elsevar copy = Object.create(null)Object.getOwnPropertyNames(obj).forEach(function(key) {Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key))})return copy
}

  会深拷贝基本类型,但是对于复杂类型也只是浅拷贝,测试代码如下:

const a = {'string': 1,'arr': [1],
}
const b = clone(a);
b.arr[0] = 2;
b.string = 2;
console.log(a); // {string:1,arr:[2]}
const c = a;
c.arr[0] = 3;
c.string = 3;
console.log(a); // {string:3,arr:[3]}

  总之,基本上相当于返回一个fs模块。

  无论如何,graceful-js都是输出patch后的fs模块,先不看同步/异步close,主要看patch方法是如何对原生API进行封装的,整理后源码如下:

function patch(fs) {// Everything that references the open() function needs to be in here// 跨平台兼容处理
    polyfills(fs)fs.gracefulify = patch;// 遗留名字fs.FileReadStream = ReadStream; // Legacy name.fs.FileWriteStream = WriteStream; // Legacy name.// 创建流fs.createReadStream = createReadStreamfs.createWriteStream = createWriteStreamvar fs$readFile = fs.readFile;fs.readFile = readFile;// 读取文件function readFile(path, options, cb) { /*...*/ }var fs$writeFile = fs.writeFile;fs.writeFile = writeFile;// 写文件function writeFile(path, data, options, cb) { /*...*/ }var fs$appendFile = fs.appendFile;if (fs$appendFile)fs.appendFile = appendFile;// 文件添加内容function appendFile(path, data, options, cb) { /*...*/ }var fs$readdir = fs.readdir;fs.readdir = readdir;// 读取目录function readdir(path, options, cb) { /*...*/ }function go$readdir(args) { /*...*/ }if (process.version.substr(0, 4) === 'v0.8') { /*...*/ }// 流处理// 可读的流var fs$ReadStream = fs.ReadStream;ReadStream.prototype = Object.create(fs$ReadStream.prototype);ReadStream.prototype.open = ReadStream$open;// 可写的流var fs$WriteStream = fs.WriteStream;WriteStream.prototype = Object.create(fs$WriteStream.prototype);WriteStream.prototype.open = WriteStream$open;fs.ReadStream = ReadStreamfs.WriteStream = WriteStreamfunction ReadStream(path, options) { /*...*/ }function ReadStream$open() { /*...*/ }function WriteStream(path, options) { /*...*/ }function WriteStream$open() { /*...*/ }function createReadStream(path, options) { /*...*/ }function createWriteStream(path, options) { /*...*/ }var fs$open = fs.open;fs.open = open;// 以某种形式打开文件function open(path, flags, mode, cb) { /*...*/ }return fs
}

  基本上文件操作API均有涉及,兼容处理这里不讨论。

  tips:以fs$***开头的变量均为原生API,例如fs$readFile代表原生的fs.readFile

  tips:源码有些写法真的僵硬,进行了一些优化增加可读性

  功能主要分为下列几块:

1、读取文件全部内容

2、写入数据到文件

3、向文件添加数据

4、读取目录

5、打开文件

6、流相关

  依次进行讲解。

文件读取:readFile

  源码如下:

function readFile(path, options, cb) {// options参数可选// 若第二参数为函数 代表省略了options参数if (typeof options === 'function')cb = options, options = null;// 调用原生的fs.readFilereturn go$readFile(path, options, cb)function go$readFile(path, options, cb) {return fs$readFile(path, options, function(err) {// 如果出错记录下来if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) {// 分别为fs模块类型 参数
                enqueue([go$readFile, [path, options, cb]])} else {if (typeof cb === 'function')cb.apply(this, arguments)retry()}})}
}// 记录错误
function enqueue(elem) {debug('ENQUEUE', elem[0].name, elem[1])queue.push(elem)
}// 重试之前产生报错的行为
function retry() {var elem = queue.shift()if (elem) {debug('RETRY', elem[0].name, elem[1])elem[0].apply(null, elem[1])}
}

  总结一下graceful-fs的优雅行为:

1、底层仍然调用的是nodejs原生API

2、当某个fs行为出错,该fs操作类型与参数会被记录下来

3、当某个fs行为成功执行,会尝试将最早出错的行为取出并再次执行,出错会再次被记录

  其余方法诸如writeFile、appendFile、readdir均与此类似,而流的抽象接口也并没有做什么额外操作,只是对读写操作中的open进行了上述加工,这里就不进行讲解了。

close/closeSync

  这两个方法用了大量注释,我还以为有啥特殊功能,代码如下:

// Always patch fs.close/closeSync, because we want to
// retry() whenever a close happens *anywhere* in the program.
// This is essential when multiple graceful-fs instances are
// in play at the same time.
module.exports.close =fs.close = (function(fs$close) {return function(fd, cb) {return fs$close.call(fs, fd, function(err) {// 关闭之前进行重试一次if (!err)retry()if (typeof cb === 'function')cb.apply(this, arguments)})}})(fs.close)module.exports.closeSync =fs.closeSync = (function(fs$closeSync) {return function(fd) {// Note that graceful-fs also retries when fs.closeSync() fails.// Looks like a bug to me, although it's probably a harmless one.var rval = fs$closeSync.apply(fs, arguments)retry()return rval}})(fs.closeSync)

  其实这里的注释还是蛮有味道的,尤其是下面的closeSync,第一次见源码注释带有作者第一人称的特殊解释(me)

  至此,grace-ful模块解析完成,其实内容并没有多复杂。

转载于:https://www.cnblogs.com/QH-Jimmy/p/8043466.html

.10-浅析webpack源码之graceful-fs模块相关推荐

  1. .17-浅析webpack源码之compile流程-入口函数run

    本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => {const startTime = Date.now() ...

  2. webpack源码解析七(optimization)

    前言 前面我们写了几篇文章用来介绍webpack源码,跟着官网结合demo把整个webpack配置撸了一遍: webpack源码解析一 webpack源码解析二(html-webpack-plugin ...

  3. webpack 源码分析系列 ——loader

    想要更好的格式阅读体验,请查看原文:webpack 源码分析系列 --loader 为什么需要 loader webpack是一个用于现代 JavaScript 应用程序的静态模块打包工具.内部通过构 ...

  4. webpack源码阅读——npm脚本运行webpack与命令行输入webpack的区别

    原文地址:webpack源码阅读--npm脚本执行webpack与命令行输入webpack执行的区别 如有错误,欢迎指正! webpack是目前被大家广为使用的模块打包器.从命令行输入webpack或 ...

  5. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  6. webpack 源码泄露

    0x01 漏洞简介 webpack是一个JavaScript应用程序的静态资源打包器(module bundler).它会递归构建一个依赖关系图(dependency graph),其中包含应用程序需 ...

  7. FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程

    文章目录 1. 前言 2. 源码分析 2.1 sofia 模块的加载 2.2 呼入的处理流程 1. 前言 SIP(Session Initiation Protocol) 是应用层的信令控制协议,有许 ...

  8. webpack 源码分析(四)——complier模块

    webpack 源码分析(四)--complier模块 上一篇我们看到,webpack-cli 通过 yargs 对命令行传入的参数和配置文件里的配置项做了转换包装,然后传递给 webpack 的 c ...

  9. NeuCF源码中用到的模块(函数)

    论文:<Neural Collaborative Filtering>源码中用到的模块(函数) from keras.layers import Embedding, Input, Den ...

  10. FreeCAD源码分析:FreeCADGui模块

    FreeCAD源码分析:FreeCADGui模块 济南友泉软件有限公司 FreeCADGui项目实现了界面操作.模型显示与交互等相关功能,项目构建生成FreeCAD(_d).dll动态链接库. Fre ...

最新文章

  1. 大数据处理语言U-SQL介绍
  2. cdf2rdf--复对角矩阵转化为实对角矩阵
  3. 在运行时访问工件的Maven和SCM版本
  4. java导出hbase表数据_通用MapReduce程序复制HBase表数据
  5. python收取wss数据_大宗商品现货数据不好拿?商品季节性难跟踪?Python爬虫一键解决没烦恼...
  6. yml文件tab 空格_YAML 文件介绍
  7. C Tricks(一)—— 一维数组变二维数组
  8. iBATIS In Action:iBATIS的安装和配置
  9. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
  10. win10固态硬盘分区 整数_固态硬盘先装系统还是先4k对齐?
  11. 按首字母排序(汉字、英文、数字)简单实现
  12. LIN总线协议详解4(进度表)
  13. secure CRT 信号灯超时时间已到
  14. Matlab中imhist函数的使用及图像直方图的概念
  15. 激活工具带毒感染量近60万,而北京等四城市用户不被攻击
  16. 小米无线wifi代理服务器,小米路由器Mini无线中继(桥接)设置教程
  17. WM_SIZING 使用说明
  18. iPhone屏幕数据
  19. 基于视觉无人机的高速公路违章识别技术
  20. 【4500字归纳总结】一名软件测试工程师需要掌握的技能大全

热门文章

  1. 安卓控件显示等宽字体的办法
  2. 奇怪的规律:飞机事故总是凑在一段时间内
  3. linux cmake 快速安装
  4. wpf mysql课程设计_使用 WPF 和 MySQL 搭建小型人资管理系统——主要页面
  5. idea 自动生成mybaits_Intellij idea中使用Mybatis插件Mybatis Generator
  6. java int 原子_java中的原子操作类AtomicInteger及其实现原理
  7. python函数传递列表_python传递列表作为函数参数
  8. mysql触发器信号给qt_利用回调函数实现DLL与Qt主程序的数据交互,进一步实现对Qt主程序中的信号触发 - zcabcd123的专栏 - 博客频道 - CSDN.NET...
  9. c语言汉诺塔递归算法_Python进阶之递归函数的用法及其示例
  10. java MDC_Java MDC.get方法代碼示例