至此已完成NodeJsInputFileSysten模块的讲解,下一步就是实际实用的模块:
compiler.inputFileSystem = new CachedInputFileSystem(new NodeJsInputFileSystem(), 60000);
挂载到compiler对象上的输入模块其实是带有缓存的输入模块,源码整理如下(用ES6的class重写):
class CachedInputFileSystem { ???constructor() { ???????// fileSystem => NodeJsInputFileSystem => graceful-fs => fs ???????this.fileSystem = fileSystem; ???????// 生成缓存容器 ???????this._statStorage = new Storage(duration); ???????this._readdirStorage = new Storage(duration); ???????this._readFileStorage = new Storage(duration); ???????this._readJsonStorage = new Storage(duration); ???????this._readlinkStorage = new Storage(duration); ???????this._stat = this.fileSystem.stat ? this.fileSystem.stat.bind(this.fileSystem) : null; ???????if (!this._stat) this.stat = null; ???????// ...more ???????// 自定义JSON读取 ???????if (this.fileSystem.readJson) { ???????????this._readJson = this.fileSystem.readJson.bind(this.fileSystem); ???????} else if (this.readFile) { ???????????this._readJson = function(path, callback) { /*...*/ }.bind(this); ???????} else { ???????????this.readJson = null; ???????} ???????// sync... ???} ???stat(path, callback) { ???????this._statStorage.provide(path, this._stat, callback); ???}; ???// readdir,readFile,readJson,readlink ???// sync... ???purge(what) { ???????this._statStorage.purge(what); ???????this._readdirStorage.purge(what); ???????this._readFileStorage.purge(what); ???????this._readlinkStorage.purge(what); ???????this._readJsonStorage.purge(what); ???};}module.exports = CachedInputFileSystem;
这里的核心是利用Storage来生成一个缓存容器,缓存对应的读操作。
有两个需要注意的地方。
一个是purge方法,这个是Storage的原型方法,所以暂时先放着(形参名有点意思,叫what)。
第二个是这个模块自定义了一个方法专门用来读取JSON文件,源码如下:
this._readJson = function(path, callback) { ???// fs.readFile读取文件 ???this.readFile(path, function(err, buffer) { ???????if (err) return callback(err); ???????try { ???????????// 先将字节流字符转换成utf-8格式的字符串 ???????????// 再调用JSON.parse进行解析 ???????????var data = JSON.parse(buffer.toString("utf-8")); ???????} catch (e) { ???????????return callback(e); ???????} ???????// 使用回调处理数据 ???????callback(null, data); ???});}.bind(this);
只是调用JSON.parse解析字符,这个方法只能专门处理JSON格式的数据,不然会报错。
Storage
该模块核心在于Storage对象,下面就看一看Storage内部实现,源码如下:
class Storage { ???constructor() { ???????// duration => 60000 ???????this.duration = duration; ???????this.running = new Map(); ???????this.data = new Map(); ???????this.levels = []; ???????if (duration > 0) { ???????????this.levels.push(new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set(), new Set()); ???????????// (duration - 8000) / 500 => 52000 / 500 => 104 ???????????for (var i = 8000; i < duration; i += 500) ???????????????this.levels.push(new Set()); ???????} ???????this.count = 0; ???????this.interval = null; ???????this.needTickCheck = false; ???????this.nextTick = null; ???????this.passive = true; ???????this.tick = this.tick.bind(this); ???} ???ensureTick() { /*...*/ }; ???finished(name, err, result) { /*...*/ }; ???finishedSync(name, err, result) { /*...*/ }; ???provide(name, provider, callback) { /*...*/ }; ???provideSync(name, provider) { /*...*/ }; ???tick() { /*...*/ }; ???checkTicks() { /*...*/ }; ???purge(what) { /*...*/ };}
构造函数中的Set与Map均为ES6新添加的数据结构,详情自行查阅。
其中levels数组除去本身的9个Set,根据duration的值,再次加了104个Set对象,之后看具体含义。
接下来依次讲解原型函数。
ensureTick
Storage.prototype.ensureTick = function() { ???// 第一调用进行初始化 ???// this.tick为定期执行的函数 ???// 执行间隔为 (60000 / 113)|0 = 530 ???if (!this.interval && this.duration > 0 && !this.nextTick) ???????this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length));};
可以看出这是一个初始化的方法,初始化一个定时器,间隔取决于传进来的duration。
做了一个测试,检测从8000开始到60000定时间隔的变化:
let startNum = 8000, ???startLen = 9, ???result = [];for (; startNum < 60000; startNum += 500, startLen++) { ???result.push((startNum / startLen | 0));}
输出如图:
由于levels的长度最低为9,当传入8000时会达到最大值,所以间隔一定小于0.888秒,且随着duration的值增加而减少,将duration设为100万可以发现这个间隔在500会趋于平缓,大部分暂且可以认为间隔是稳定在0.5秒~0.6秒。
checkTicks
Storage.prototype.checkTicks = function() { ???this.passive = false; ???if (this.nextTick) { ???????// 无限执行tick直到返回true ???????while (!this.tick()); ???}};
finished
Storage.prototype.finished = function(name, err, result) { ???// 获取指定名字的回调事件流 ???var callbacks = this.running.get(name); ???this.running.delete(name); ???if (this.duration > 0) { ???????// 设置进data ???????this.data.set(name, [err, result]); ???????// 获取levels的第一个Set对象 ???????var levelData = this.levels[0]; ???????// 新增count才会+1 ???????this.count -= levelData.size; ???????levelData.add(name); ???????this.count += levelData.size; ???????this.ensureTick(); ???} ???// 遍历执行回调 ???for (var i = 0; i < callbacks.length; i++) { ???????callbacks[i](err, result); ???}};
不应用的话不知道是干嘛用的。
finishedSync
Storage.prototype.finishedSync = function(name, err, result) { ???if (this.duration > 0) { ???????// ...一模一样 ???}};
provide
Storage.prototype.provide = function(name, provider, callback) { ???if (typeof name !== "string") { ???????callback(new TypeError("path must be a string")); ???????return; ???} ???var running = this.running.get(name); ???// 将回调函数加进runnning直接返回 ???if (running) { ???????running.push(callback); ???????return; ???} ???if (this.duration > 0) { ???????this.checkTicks(); ???????// 获取data中对应的事件 异步执行 ???????var data = this.data.get(name); ???????if (data) { ???????????return process.nextTick(function() { ???????????????callback.apply(null, data); ???????????}); ???????} ???} ???// 无法获取running与data时 ???this.running.set(name, running = [callback]); ???var _this = this; ???provider(name, function(err, result) { ???????_this.finished(name, err, result); ???});};
该方法会先后尝试从running与data中获取对应的事件,无法获取将设置到running中,并调用提供的provider方法。
tick
Storage.prototype.tick = function() { ???var decay = this.levels.pop(); ???for (var item of decay) { ???????this.data.delete(item); ???} ???this.count -= decay.size; ???decay.clear(); ???// 清空后头部插入 ???this.levels.unshift(decay); ???// 当没有事件时初始化条件 ???if (this.count === 0) { ???????clearInterval(this.interval); ???????this.interval = null; ???????this.nextTick = null; ???????return true; ???} else if (this.nextTick) { ???????this.nextTick += Math.floor(this.duration / this.levels.length); ???????var time = new Date().getTime(); ???????if (this.nextTick > time) { ???????????this.nextTick = null; ???????????// 初始化定时器 ???????????this.interval = setInterval(this.tick, Math.floor(this.duration / this.levels.length)); ???????????return true; ???????} ???} else if (this.passive) { ???????clearInterval(this.interval); ???????this.interval = null; ???????this.nextTick = new Date().getTime() + Math.floor(this.duration / this.levels.length); ???} else { ???????this.passive = true; ???}};
这个方法在使用中再解释吧。
purge
Storage.prototype.purge = function(what) { ???// 不传参数 ????// 清空所有数据 ???if (!what) { ???????this.count = 0; ???????clearInterval(this.interval); ???????this.nextTick = null; ???????this.data.clear(); ???????this.levels.forEach(function(level) { ???????????level.clear(); ???????}); ???} ???// 传字符串 ???// 移除data中所有以参数开头的key ????else if (typeof what === "string") { ???????for (var key of this.data.keys()) { ???????????if (key.startsWith(what)) ???????????????this.data.delete(key); ???????} ???} ???// 传数组 ???// 递归批量移除 ???else { ???????for (var i = what.length - 1; i >= 0; i--) { ???????????this.purge(what[i]); ???????} ???}};
用于清空数据的方法。
总体来说,模块内容如下:
1、levels数组 => 总数据源
2、running对象 => 存储待运行回调事件流
3、data对象 => 存储已完成事件流
4、count => 记录levels数据数量
5、interval => 当前定时器的id
6、needTick,nextTick,passive均为标记
由于没有应用,所以讲起来十分僵硬,后面的源码中会重新回来看这些方法。
.11-浅析webpack源码之Storage模块
原文地址:http://www.cnblogs.com/QH-Jimmy/p/8045361.html