.11-淺析webpack源碼之Storage模塊

来源:http://www.cnblogs.com/QH-Jimmy/archive/2017/12/16/8045361.html
-Advertisement-
Play Games

至此已完成NodeJsInputFileSysten模塊的講解,下一步就是實際實用的模塊: 掛載到compiler對象上的輸入模塊其實是帶有緩存的輸入模塊,源碼整理如下(用ES6的class重寫): 這裡的核心是利用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均為標記

 

  由於沒有應用,所以講起來十分僵硬,後面的源碼中會重新回來看這些方法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...