.10-浅析webpack源码之graceful-fs模块
在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模块相关推荐
- .17-浅析webpack源码之compile流程-入口函数run
本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => {const startTime = Date.now() ...
- webpack源码解析七(optimization)
前言 前面我们写了几篇文章用来介绍webpack源码,跟着官网结合demo把整个webpack配置撸了一遍: webpack源码解析一 webpack源码解析二(html-webpack-plugin ...
- webpack 源码分析系列 ——loader
想要更好的格式阅读体验,请查看原文:webpack 源码分析系列 --loader 为什么需要 loader webpack是一个用于现代 JavaScript 应用程序的静态模块打包工具.内部通过构 ...
- webpack源码阅读——npm脚本运行webpack与命令行输入webpack的区别
原文地址:webpack源码阅读--npm脚本执行webpack与命令行输入webpack执行的区别 如有错误,欢迎指正! webpack是目前被大家广为使用的模块打包器.从命令行输入webpack或 ...
- Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现
写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...
- webpack 源码泄露
0x01 漏洞简介 webpack是一个JavaScript应用程序的静态资源打包器(module bundler).它会递归构建一个依赖关系图(dependency graph),其中包含应用程序需 ...
- FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程
文章目录 1. 前言 2. 源码分析 2.1 sofia 模块的加载 2.2 呼入的处理流程 1. 前言 SIP(Session Initiation Protocol) 是应用层的信令控制协议,有许 ...
- webpack 源码分析(四)——complier模块
webpack 源码分析(四)--complier模块 上一篇我们看到,webpack-cli 通过 yargs 对命令行传入的参数和配置文件里的配置项做了转换包装,然后传递给 webpack 的 c ...
- NeuCF源码中用到的模块(函数)
论文:<Neural Collaborative Filtering>源码中用到的模块(函数) from keras.layers import Embedding, Input, Den ...
- FreeCAD源码分析:FreeCADGui模块
FreeCAD源码分析:FreeCADGui模块 济南友泉软件有限公司 FreeCADGui项目实现了界面操作.模型显示与交互等相关功能,项目构建生成FreeCAD(_d).dll动态链接库. Fre ...
最新文章
- 大数据处理语言U-SQL介绍
- cdf2rdf--复对角矩阵转化为实对角矩阵
- 在运行时访问工件的Maven和SCM版本
- java导出hbase表数据_通用MapReduce程序复制HBase表数据
- python收取wss数据_大宗商品现货数据不好拿?商品季节性难跟踪?Python爬虫一键解决没烦恼...
- yml文件tab 空格_YAML 文件介绍
- C Tricks(一)—— 一维数组变二维数组
- iBATIS In Action:iBATIS的安装和配置
- com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
- win10固态硬盘分区 整数_固态硬盘先装系统还是先4k对齐?
- 按首字母排序(汉字、英文、数字)简单实现
- LIN总线协议详解4(进度表)
- secure CRT 信号灯超时时间已到
- Matlab中imhist函数的使用及图像直方图的概念
- 激活工具带毒感染量近60万,而北京等四城市用户不被攻击
- 小米无线wifi代理服务器,小米路由器Mini无线中继(桥接)设置教程
- WM_SIZING 使用说明
- iPhone屏幕数据
- 基于视觉无人机的高速公路违章识别技术
- 【4500字归纳总结】一名软件测试工程师需要掌握的技能大全
热门文章
- 安卓控件显示等宽字体的办法
- 奇怪的规律:飞机事故总是凑在一段时间内
- linux cmake 快速安装
- wpf mysql课程设计_使用 WPF 和 MySQL 搭建小型人资管理系统——主要页面
- idea 自动生成mybaits_Intellij idea中使用Mybatis插件Mybatis Generator
- java int 原子_java中的原子操作类AtomicInteger及其实现原理
- python函数传递列表_python传递列表作为函数参数
- mysql触发器信号给qt_利用回调函数实现DLL与Qt主程序的数据交互,进一步实现对Qt主程序中的信号触发 - zcabcd123的专栏
- 博客频道 - CSDN.NET...
- c语言汉诺塔递归算法_Python进阶之递归函数的用法及其示例
- java MDC_Java MDC.get方法代碼示例