.11-浅析webpack源码之Storage模块

  至此已完成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均为标记

  由于没有应用,所以讲起来十分僵硬,后面的源码中会重新回来看这些方法。

时间: 2024-08-30 14:28:05

.11-浅析webpack源码之Storage模块的相关文章

.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_PATC

.15-浅析webpack源码之WebpackOptionsApply模块之插件王中王

总体过了一下后面的流程,发现Compiler模块确实不适合单独讲解,这里继续讲解后面的代码: compiler.options = new WebpackOptionsApply().process(options, compiler); 这行代码与之前设置options默认值非常相似,但是复杂程度根本不是一个次元的. 这一节只能简单的看一眼内部到底有多少东西,整理后源码如下: "use strict"; const OptionsApply = require("./Opt

.12-浅析webpack源码之NodeWatchFileSystem模块总览

剩下一个watch模块,这个模块比较深,先大概过一下整体涉及内容再分部讲解. 流程图如下: NodeWatchFileSystem const Watchpack = require("watchpack"); class NodeWatchFileSystem { constructor(inputFileSystem) { this.inputFileSystem = inputFileSystem; this.watcherOptions = { aggregateTimeout

nginx源码分析--nginx模块解析

nginx的模块非常之多,可以认为所有代码都是以模块的形式组织,这包括核心模块和功能模块,针对不同的应用场合,并非所有的功能模块都要被用到,附录A给出的是默认configure(即简单的http服务器应用)下被连接的模块,这里虽说是模块连接,但nginx不会像apache或lighttpd那样在编译时生成so动态库而在程序执行时再进行动态加载,nginx模块源文件会在生成nginx时就直接被编译到其二进制执行文件中,所以如果要选用不同的功能模块,必须对nginx做重新配置和编译.对于功能模块的选

nginx源码分析之模块初始化

在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要通过结合源码来分析模块的初始化过程. 稍微了解nginx的人都知道nginx是高度模块化的,各个功能都封装在模块中,而各个模块的初始化则是根据配置文件来进行的,下面我们会看到nginx边解析配置文件中的指令,边初始化指令所属的模块,指令其实就是指示怎样初始化模块的. 模块初始化框架 模块的初始化主要

兄弟连区块链教程open-ethereum-pool矿池源码分析unlocker模块

兄弟连区块链教程open-ethereum-pool以太坊矿池源码分析unlocker模块open-ethereum-pool以太坊矿池-unlocker模块 unlocker模块配置 json"unlocker": {????"enabled": false,????"poolFee": 1.0,????"poolFeeAddress": "",????"donate": true,?

webpack源码-依赖收集

webpack源码-依赖收集 version:3.12.0 程序主要流程: 触发make钩子 Compilation.js 执行EntryOptionPlugin 中注册的make钩子 执行compilation.addEntry 执行compilation._addModuleChain Compilation.js 执行moduleFactory.create(this.semaphore.avaiable 初始化为100) Compilation.js 执行this.buildModule

thttpd源码解析 定时器模块

thttpd源码解析 定时器模块 thttpd是非常轻量级的http服务器,可执行文件仅50kB.名称中的第一个t表示tiny, turbo, 或throttling 与lighttpd.memcached.redis相比非常小巧,仅有不到8k行,而后三者大小分别为:60k,13k,86k 支持HTTP/1.1和CGI:采用IO复用实现,单线程,可移植:实现了基于URL的文件流量限制功能 特别适用于大量静态数据访问的场景,如图片存储 2004年已经停止维护,有一个关于X-Forwarded-Fo

webpack源码分析——配置调试环境

无论是阅读webpack源码,还是编写webpack的plugin和loader,配置调试环境都是很有必要的.weabpack的运行环境是nodejs,调试webpack就是调试nodejs程序.我们平时使用的IDE如eclipse.webstorm都支持nodejs的调试.本文以eclipse(Version: Oxygen.1a Release (4.7.1a))为例,进行讲解. 在这个例子里面,我们使用webpack <entry> [<entry>] <output&