.37-淺析webpack源碼之事件流make(4)

来源:https://www.cnblogs.com/QH-Jimmy/archive/2018/03/02/8492430.html
-Advertisement-
Play Games

趕緊完結這個系列咯,webpack4都已經出正式版了。 之前的代碼搜索到js文件的對應loader,並添加到了對象中返回,流程如下: 這個對象的request將入口文件的路徑與loader拼接起來並用!分割,所有的屬性基本上都與路徑相關。 after-resolve事件流 這裡會觸發after-re ...


  趕緊完結這個系列咯,webpack4都已經出正式版了。

  之前的代碼搜索到js文件的對應loader,並添加到了對象中返回,流程如下:

this.plugin("factory", () => (result, callback) => {
    let resolver = this.applyPluginsWaterfall0("resolver", null);

    // Ignored
    if (!resolver) return callback();

    // 這裡的data就是返回的大對象
    /*
        data => 
        {
            context: 'd:\\workspace\\doc',
            request: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js',
            dependencies: [SingleEntryDependency],,
            userRequest: 'd:\\workspace\\doc\\input.js',
            rawRequest: './input.js',
            loaders: [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ],
            resource: 'd:\\workspace\\doc\\input.js',
            還有package.json與parser相關的屬性
        }
    */
    resolver(result, (err, data) => {
        if (err) return callback(err);

        // Ignored
        if (!data) return callback();

        // direct module
        if (typeof data.source === "function")
            return callback(null, data);

        this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
            // ...
        })
    })
})

  這個對象的request將入口文件的路徑與loader拼接起來並用!分割,所有的屬性基本上都與路徑相關。

after-resolve事件流

  這裡會觸發after-resolve事件流,註入地點如下:

const matchJson = /\.json$/i;
params.normalModuleFactory.plugin("after-resolve", (data, done) => {
    // if this is a json file and there are no loaders active, we use the json-loader in order to avoid parse errors
    // @see https://github.com/webpack/webpack/issues/3363
    if (matchJson.test(data.request) && data.loaders.length === 0) {
        data.loaders.push({
            loader: jsonLoaderPath
        });
    }
    done(null, data);
});

  註釋已經寫的很明白了,這裡是檢測待處理文件類型是否是json文件,如果是並且沒有對應的loader,就將內置的json-loader作為loader。

callback

  接下來看回調函數內容:

this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
    if (err) return callback(err);

    // Ignored
    if (!result) return callback();
    // 無此事件流 返回undefined
    let createdModule = this.applyPluginsBailResult("create-module", result);
    if (!createdModule) {

        if (!result.request) {
            return callback(new Error("Empty dependency (no request)"));
        }
        // 創建
        createdModule = new NormalModule(
            result.request,
            result.userRequest,
            result.rawRequest,
            result.loaders,
            result.resource,
            result.parser
        );
    }
    // 無此事件流
    createdModule = this.applyPluginsWaterfall0("module", createdModule);

    return callback(null, createdModule);
});

  這裡的兩個事件流都是沒有的,所以只需要看那個創建類的過程,然後就直接返回了。

  只看下構造函數:

class NormalModule extends Module {
    /* 
        request => 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js'
        userRequest => 'd:\\workspace\\doc\\input.js'
        rawRequest => './input.js'
        loaders => [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ]
        resource => 'd:\\workspace\\doc\\input.js'
        parser => [Parser]
    */
    constructor(request, userRequest, rawRequest, loaders, resource, parser) {
        super();
        this.request = request;
        this.userRequest = userRequest;
        this.rawRequest = rawRequest;
        this.parser = parser;
        this.resource = resource;
        this.context = getContext(resource);
        this.loaders = loaders;
        this.fileDependencies = [];
        this.contextDependencies = [];
        this.warnings = [];
        this.errors = [];
        this.error = null;
        this._source = null;
        this.assets = {};
        this.built = false;
        this._cachedSource = null;
    }

    // ...原型方法
}

  只有一個getContext方法是執行的,這個方法比較簡單,過一下就行:

exports.getContext = function getContext(resource) {
    var splitted = splitQuery(resource);
    return dirname(splitted[0]);
};

// 切割參數
function splitQuery(req) {
    var i = req.indexOf("?");
    if (i < 0) return [req, ""];
    return [req.substr(0, i), req.substr(i)];
}

// 返回目錄
// d:\\workspace\\doc\\input.js => d:\\workspace\\doc(反斜杠這裡有轉義)
function dirname(path) {
    if (path === "/") return "/";
    var i = path.lastIndexOf("/");
    var j = path.lastIndexOf("\\");
    var i2 = path.indexOf("/");
    var j2 = path.indexOf("\\");
    var idx = i > j ? i : j;
    var idx2 = i > j ? i2 : j2;
    if (idx < 0) return path;
    if (idx === idx2) return path.substr(0, idx + 1);
    return path.substr(0, idx);
}

  返回了入口文件路徑的目錄,也就是上下文。

  這樣一路回調返回,回到了最初的原型方法create:

// NormalModuleFactory
const factory = this.applyPluginsWaterfall0("factory", null);

// Ignored
if (!factory) return callback();

factory(result, (err, module) => {
    // 這裡的module就是new出來的NormalModule對象
    if (err) return callback(err);
    // this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
    // 由於預設情況下unsafeCache為true 所以這個函數預設一直返回true
    if (module && this.cachePredicate(module)) {
        dependencies.forEach(d => d.__NormalModuleFactoryCache = module);
    }

    callback(null, module);
});

  這裡對之前處理的入口文件對象做了緩存。

  返回又返回,回到了Compilation對象中:

_addModuleChain(context, dependency, onModule, callback) {
    // ...

    this.semaphore.acquire(() => {
        // 從這裡出來
        moduleFactory.create({
            contextInfo: {
                issuer: "",
                compiler: this.compiler.name
            },
            context: context,
            dependencies: [dependency]
        }, (err, module) => {
            if (err) {
                this.semaphore.release();
                return errorAndCallback(new EntryModuleNotFoundError(err));
            }

            let afterFactory;
            // 不存在的屬性
            if (this.profile) {
                if (!module.profile) {
                    module.profile = {};
                }
                afterFactory = Date.now();
                module.profile.factory = afterFactory - start;
            }

            const result = this.addModule(module);
            // ...
        });
    });
}

  常規的錯誤處理,然後判斷了下profile屬性,這個屬性大概是用計算前面factory整個事件流的觸發時間。

  總之,接著調用了addModule原型方法

addModule

// 第二個參數未傳
addModule(module, cacheGroup) {
    // 這裡調用的是原型方法
    /*
        identifier() {
            return this.request;
        }
    */
    // 愚蠢的方法
    const identifier = module.identifier();
    // 空對象沒有的
    if (this._modules[identifier]) {
        return false;
    }
    // 緩存名是'm' + request
    const cacheName = (cacheGroup || "m") + identifier;
    // 如果有緩存的情況下
    if (this.cache && this.cache[cacheName]) {
        // ...
    }
    // 這個方法是在父類上面
    // 作用是清空屬性
    // 然而並沒有什麼屬性可以清 跳過
    module.unbuild();
    // 將類分別加入各個對象/數組
    this._modules[identifier] = module;
    if (this.cache) {
        this.cache[cacheName] = module;
    }
    this.modules.push(module);
    return true;
}

  這個方法其實並沒有做什麼實際的東西,簡單來講只是添加了緩存。

  接下來看下麵的代碼:

_addModuleChain(context, dependency, onModule, callback) {
    // ...

    this.semaphore.acquire(() => {
        moduleFactory.create({
            contextInfo: {
                issuer: "",
                compiler: this.compiler.name
            },
            context: context,
            dependencies: [dependency]
        }, (err, module) => {
            // ...
            // 返回一個true
            const result = this.addModule(module);
            // 跳過下麵兩步
            if (!result) {
                // ...
            }

            if (result instanceof Module) {
                // ...
            }
            // 進入這裡
            /*
                (module) => {
                    entry.module = module;
                    this.entries.push(module);
                    module.issuer = null;
                }
            */
            onModule(module);

            this.buildModule(module, false, null, null, (err) => {
                if (err) {
                    this.semaphore.release();
                    return errorAndCallback(err);
                }

                if (this.profile) {
                    const afterBuilding = Date.now();
                    module.profile.building = afterBuilding - afterFactory;
                }

                moduleReady.call(this);
            });

            function moduleReady() {
                this.semaphore.release();
                this.processModuleDependencies(module, err => {
                    if (err) {
                        return callback(err);
                    }

                    return callback(null, module);
                });
            }
        });
    });
}

  這個onModule來源於方法的第三個參數,比較簡單,沒做什麼事,然後繼續跑調用了原型方法buildModule。

buildModule

// this.buildModule(module, false, null, null, (err) => {})
buildModule(module, optional, origin, dependencies, thisCallback) {
    // 無此事件流
    this.applyPlugins1("build-module", module);
    if (module.building) return module.building.push(thisCallback);
    const building = module.building = [thisCallback];

    function callback(err) {
        module.building = undefined;
        building.forEach(cb => cb(err));
    }
    module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (error) => {
        // ...
    });
}

  總的來說並沒有做什麼事,傳進來的callback被封裝並帶入build方法的回調中。

module.build

// options為添加了預設參數的配置對象webpack.config.js
build(options, compilation, resolver, fs, callback) {
    this.buildTimestamp = Date.now();
    this.built = true;
    this._source = null;
    this.error = null;
    this.errors.length = 0;
    this.warnings.length = 0;
    this.meta = {};

    return this.doBuild(options, compilation, resolver, fs, (err) => {
        // ...
    });
}

  一串串的賦值,又是doBuild方法。

 

module.doBuild

doBuild(options, compilation, resolver, fs, callback) {
    this.cacheable = false;
    const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);

    runLoaders({
        resource: this.resource,
        loaders: this.loaders,
        context: loaderContext,
        readResource: fs.readFile.bind(fs)
    }, (err, result) => {
        // ...
    });
}

  呃,已經連續調用好多個函數了。

createLoaderContext

createLoaderContext(resolver, options, compilation, fs) {
    const loaderContext = {
        // ..
    };
    compilation.applyPlugins("normal-module-loader", loaderContext, this);
    // 無此值
    if (options.loader)
        Object.assign(loaderContext, options.loader);

    return loaderContext;
}

  這裡先不關心這個loaderContext裡面到底有什麼,因為後面會調用的,直接看事件流'normal-module-loader'。

// LoaderTargetPlugin.js
compilation.plugin("normal-module-loader", (loaderContext) => loaderContext.target = this.target);

  這裡由於配置對象沒有target參數,所以這個沒有用。

// LoaderPlugin.js
compilation.plugin("normal-module-loader", (loaderContext, module) => {
    loaderContext.loadModule = function loadModule(request, callback) {
        // ...
    };
});

  這個僅僅是加了個屬性方法,跳過。

  最後返回了一個包含很多方法的對象loaderContext。

  第二步是調用runLoaders方法,將之前生成的對象傳進去,這裡終於要調用loader對文件進行處理了!!!!

  下節可以有乾貨,這節流水賬先這樣了。


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

-Advertisement-
Play Games
更多相關文章
  • 一. 安裝以及環境配置 安裝路徑 http://nodejs.cn/download/ 多種環境選擇 環境變數的配置 Step1 先檢查環境變數中的系統變數裡面的path,查看是否加入了node.js 例如我的node.js安裝路徑是C:\Program Files\nodejs 那麼,這個path ...
  • 在vue的’項目中遇到了需要使用富文本編輯器的需求,在github上看了很多vue封裝的editor插件,很多對圖片上傳和視頻上傳的支持並不是很好,最終還是決定使用UEditor。 這類的文章網上有很多,我進行了摸索、手寫代碼、彙總、排版,形成了這篇文章。 下載對應的UEditor源碼 首先,去官網 ...
  • 之前學習了nodejs的基礎,今天安裝Express框架,在安裝的過程中出現的一些問題,在這裡記錄下來 1:安裝某個nodejs模塊,使用install子命令 2:檢測安裝的版本:一般來說 express -V 和express -v都是可以的,但是我的要用express --version(我的不 ...
  • 文檔流的概念指什麼?有哪種方式可以讓元素脫離文檔流? 文檔流,指的是元素排版佈局過程中,元素會自動從左往右,從上往下的流式排列。並最終窗體自上而下分成一行行,併在每行中按從左到右的順序排放元素。脫離文檔流即是元素打亂了這個排列,或是從排版中拿走。 讓元素脫離文檔流的方法有:浮動和定位。 在 CSS ...
  • 頁面HTML: css代碼: ...
  • 如題,以前都是給客戶提供安卓和iOS兩個二維碼,實在覺得麻煩,就是一勞永逸了一下。不會傳附件,需要相關素材的可以私我。 ...
  • 在大多數的瀏覽器中都有實現網頁全屏顯示的功能,並且大部分瀏覽器實現全屏顯示和退出全屏顯示的快捷鍵通常是F11和Esc兩個按鍵。如今,W3C已經制定了關於網頁全屏顯示的API,利用這個API 可以實現網頁的全屏顯示,並且還能將某個特定的元素設置為全屏顯示,在各瀏覽器的相容性:google chrome... ...
  • 看了網上很多資料,對vue的computed講解自己看的都不是很清晰,今天忙裡抽閑,和同事們又閑聊起來,對computed這個屬性才有了一個稍微比較清晰的認識,下麵的文章有一部分是轉自: https://www.w3cplus.com/vue/vue-computed-intro.html © w3 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...