.35-淺析webpack源碼之babel-loader入口文件路徑讀取

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

哈哈,上首頁真難,每次都被秒下,心疼自己1秒~ 這裡補充一個簡要圖,方便理解流程: 在處理./input.js入口文件時,在類型判斷被分為普通文件,所以走的文件事件流,最後拼接得到文件的絕對路徑。 但是對應"babel-loader"這個字元串,在如下正則中被判定為模塊類型: 因此,參考第33節的流 ...


  哈哈,上首頁真難,每次都被秒下,心疼自己1秒~

  這裡補充一個簡要圖,方便理解流程:

  在處理./input.js入口文件時,在類型判斷被分為普通文件,所以走的文件事件流,最後拼接得到文件的絕對路徑。

  但是對應"babel-loader"這個字元串,在如下正則中被判定為模塊類型:

// Resolver.js
var notModuleRegExp = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i;
Resolver.prototype.isModule = function isModule(path) {
    return !notModuleRegExp.test(path);
};

  因此,參考第33節的流程圖,在ModuleKindPlugin插件中,會走新的事件流,如下:

ModuleKindPlugin.prototype.apply = function(resolver) {
    var target = this.target;
    resolver.plugin(this.source, function(request, callback) {
        // 這裡request.module為true 繼續走下麵的代碼
        if (!request.module) return callback();
        var obj = Object.assign({}, request);
        delete obj.module;
        // target => raw-module
        resolver.doResolve(target, obj, "resolve as module", createInnerCallback(function(err, result) {
            if (arguments.length > 0) return callback(err, result);

            // Don't allow other alternatives
            callback(null, null);
        }, callback));
    });
};

  進入raw-module事件流!

 

raw-module事件流

  事件流定義如下:

// raw-module
// 預設為空數組
moduleExtensions.forEach(function(item) {
    plugins.push(new ModuleAppendPlugin("raw-module", item, "module"));
});
// 垃圾插件
if (!enforceModuleExtension)
    plugins.push(new TryNextPlugin("raw-module", null, "module"));

  這裡沒有任何操作,直接進入module事件流。

 

module事件流 => ModulesInHierachicDirectoriesPlugin

  定義地點如下:

// module
// modules => [ [ 'node_modules' ] ]
modules.forEach(function(item) {
    // 進這個分支
    if (Array.isArray(item))
        plugins.push(new ModulesInHierachicDirectoriesPlugin("module", item, "resolve"));
    else
        plugins.push(new ModulesInRootPlugin("module", item, "resolve"));
});

  這個modules是預設的一個數組,內容為node_modules,後來二次包裝變成個二維數組了。

  總之不管那麼多,直接看插件內容:

ModulesInHierachicDirectoriesPlugin.prototype.apply = function(resolver) {
    var directories = this.directories;
    var target = this.target;
    resolver.plugin(this.source, function(request, callback) {
        var fs = this.fileSystem;
        // 早該這樣了 一堆callback誰分的清啊
        var topLevelCallback = callback;
        // getPaths獲取進程路徑各級目錄
        // D:\workspace\doc => ['d:\\workspace\\doc','d:\\workspace','d:']
        // 這個map+reduce總的來說就是拼接路徑
        /* 
            最後會生成
            [ 
                'D:\\workspace\\doc\\node_modules',
                'D:\\workspace\\node_modules',
                'D:\\node_modules' 
            ]
        */
        var addrs = getPaths(request.path).paths.map(function(p) {
            return directories.map(function(d) {
                return this.join(p, d);
            }, this);
        }, this).reduce(function(array, p) {
            array.push.apply(array, p);
            return array;
        }, []);
        // 開始讀取每一個拼接成的數組
        forEachBail(addrs, function(addr, callback) {
            fs.stat(addr, function(err, stat) {
                // 當讀取到一個有效路徑時就進入下一個事件流
                if (!err && stat && stat.isDirectory()) {
                    var obj = Object.assign({}, request, {
                        path: addr,
                        request: "./" + request.request
                    });
                    var message = "looking for modules in " + addr;
                    return resolver.doResolve(target, obj, message, createInnerCallback(callback, topLevelCallback));
                }
                if (topLevelCallback.log) topLevelCallback.log(addr + " doesn't exist or is not a directory");
                if (topLevelCallback.missing) topLevelCallback.missing.push(addr);
                return callback();
            });
        }, callback);
    });
};

  這裡的方法很簡單很暴力,直接拆分當前的目錄,然後跟node_modules進行拼接,然後嘗試讀取每一個路徑。

  當讀取成功而且該目錄是一個文件夾時,就會把信息加入對象併進入下一個事件流。

  這個事件流的名字是resolve……是的,又要進入新的一輪迴圈,這次會以普通文件的形式讀取,所以之前在獲取模塊類型的第一時間需要刪除module屬性。

 

  再次進入doResolve中,這裡只用文字描述流程,request變成了./babel-loader,path為node_modules文件夾的路徑。

1、ParsePlugin類型判斷

  由於在上面的代碼中,request被手動添加了./的首碼,所以在模塊判斷正則中被判定為非模塊。

2、DescriptionFilePlugin

  由於path修改,所以在讀取package.json時會直接讀取node_modules/babel-loader/package.json文件。

  但是在第一次讀取會失敗,因為並不存在node_modules/package.json文件,修正過程看下麵。

3、JoinRequestPlugin

  在這個插件,配置文件讀取路徑會被修正,代碼如下:

JoinRequestPlugin.prototype.apply = function(resolver) {
    var target = this.target;
    resolver.plugin(this.source, function(request, callback) {
        /* 
            path => D:\workspace\node_modules
            request => ./babel-loader
            拼接後得到 D:\\workspace\\node_modules\\babel-loader
        */
        var obj = Object.assign({}, request, {
            path: resolver.join(request.path, request.request),
            relativePath: request.relativePath && resolver.join(request.relativePath, request.request),
            request: undefined
        });
        resolver.doResolve(target, obj, null, callback);
    });
};

 4、FileExistsPlugin

  由於上面的修正,這個插件的讀取會有一些問題:

FileExistsPlugin.prototype.apply = function(resolver) {
    var target = this.target;
    resolver.plugin(this.source, function(request, callback) {
        var fs = this.fileSystem;
        var file = request.path;
        // file => D:\\workspace\\node_modules\\babel-loader
        fs.stat(file, function(err, stat) {
            if (err || !stat) {
                if (callback.missing) callback.missing.push(file);
                if (callback.log) callback.log(file + " doesn't exist");
                return callback();
            }
            // 由於這是一個文件夾 所以會進入這個分支
            if (!stat.isFile()) {
                if (callback.missing) callback.missing.push(file);
                if (callback.log) callback.log(file + " is not a file");
                return callback();
            }
            this.doResolve(target, request, "existing file: " + file, callback, true);
        }.bind(this));
    });
};

  由於讀取到這是一個文件夾,所以不會進入最後的事件流,而是直接調用無參回調函數,再次重新嘗試讀取。

 

文件夾讀取 => existing-drectory

  由於這裡會讀取文件夾類型,所以順便把文件夾的事件流分支過一下。

  相關的核心事件流只有existing-directory,如下:

// existing-directory
// 這個忽略
plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve"));
// 預設 mainFields => ["browser", "module", "main"]
mainFields.forEach(function(item) {
    plugins.push(new MainFieldPlugin("existing-directory", item, "resolve"));
});
// 預設 mainFiles => ["index"]
mainFiles.forEach(function(item) {
    plugins.push(new UseFilePlugin("existing-directory", item, "undescribed-raw-file"));
});

  除去第一個垃圾插件,後面兩個可以過一下。

MainFieldPlugin

  這個插件主要獲取loader的入口文件相對路徑。

// existing-directory
// 這個忽略
plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve"));
/*
    預設mainFields => 
    { name: 'loader', forceRelative: true }
    { name: 'main', forceRelative: true }
*/
mainFields.forEach(function(item) {
    plugins.push(new MainFieldPlugin("existing-directory", item, "resolve"));
});
MainFieldPlugin.prototype.apply = function(resolver) {
    var target = this.target;
    // options => ["browser", "module", "main"]
    var options = this.options;
    resolver.plugin(this.source, function mainField(request, callback) {
        if (request.path !== request.descriptionFileRoot) return callback();
        var content = request.descriptionFileData;
        // path.basename => 獲取path的最後一部分
        // D:\workspace\node_modules\babel-loader\package.json => package.json
        var filename = path.basename(request.descriptionFilePath);
        var mainModule;
        // loader、main
        var field = options.name;
        if (Array.isArray(field)) {
            var current = content;
            for (var j = 0; j < field.length; j++) {
                if (current === null || typeof current !== "object") {
                    current = null;
                    break;
                }
                current = current[field[j]];
            }
            if (typeof current === "string") {
                mainModule = current;
            }
        } else {
            // 獲取配置文件中對應鍵的值
            if (typeof content[field] === "string") {
                mainModule = content[field];
            }
        }
        if (!mainModule) return callback();
        if (options.forceRelative && !/^\.\.?\//.test(mainModule))
            mainModule = "./" + mainModule;
        // 定義request的值
        var obj = Object.assign({}, request, {
            request: mainModule
        });
        return resolver.doResolve(target, obj, "use " + mainModule + " from " + options.name + " in " + filename, callback);
    });
}

  而在babel-loader中,有一個名為main的鍵,值為'lib/index.js',與目錄拼接後,剛好是babel-loader這個模塊的入口文件的絕對路徑。

  一旦在package.json中找到了對應的值,就會跳過下一個插件,直接進入最終的事件流。

  但是為了完整,還是看一眼,當package.json中沒有對入口文件的路徑進行定義時,會進入MainFieldPlugin插件,源碼如下:

UseFilePlugin.prototype.apply = function(resolver) {
    var filename = this.filename;
    var target = this.target;
    resolver.plugin(this.source, function(request, callback) {
        // 預設filename => "index"
        // 直接對目錄與預設入口文件名進行拼接
        var filePath = resolver.join(request.path, filename);
        // 重定義鍵
        var obj = Object.assign({}, request, {
            path: filePath,
            relativePath: request.relativePath && resolver.join(request.relativePath, filename)
        });
        resolver.doResolve(target, obj, "using path: " + filePath, callback);
    });
};

  很簡單,直接拼接預設入口文件名與目錄,接下來流程如下:

mainFiles.forEach(function(item) {
    plugins.push(new UseFilePlugin("existing-directory", item, "undescribed-raw-file"));
});

// undescribed-raw-file
// 重新走一邊文件類型的流程
plugins.push(new DescriptionFilePlugin("undescribed-raw-file", descriptionFiles, "raw-file"));
plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));

  可以看到,拼接完後,將以文件類型的形式嘗試重新讀取新路徑。

 

  至此,模塊的入口文件路徑讀取過程解析完畢,基本上其他loader、框架、庫等都是按照這個模式讀取到入口文件的。


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

-Advertisement-
Play Games
更多相關文章
  • canvas是HTML5中的一個新元素,這個元素可以通過JavaScript用來繪製圖形。例如可以用它來畫圖、合成圖象、做一些動畫等。 通常呢,我們在canvas上畫圖的方法是使用Image對象。基本上一些典型的圖片格式(png,jpg,gif等都沒有問題。好,那接下來我們就來使用它。 這是在我一個 ...
  • "源文件" ...
  • 年前,想在自己的網站首頁搞點過年喜慶的氣氛,在網上找素材無意中發現工業機器人網的首頁有一個掉金幣的JS特效,感覺挺好玩的,也是我想要找的效果,廢話少說,我還是一步步跟大家分析和實現這個效果,原理比較簡單。 大家可以看工業機器人網官網首頁的效果:http://www.robot-shop.cn/ 所需 ...
  • /* 一、generator函數的定義 1.Generator 函數是 ES6 提供的一種非同步編程解決方案,語法行為與傳統函數完全不同 2.形式上,Generator 函數是一個普通函數,但是有兩個特征。一是,function關鍵字與函數名之間有一個星號;二是,函數體內部使用yield表達式,定義不... ...
  • 記錄自己在工作中,時不時使用,每次都要去查一下的基礎方法。以及ES6經常使用的方法 一、Array 1、concat 合併數組 2、shift 獲取數組第一個元素 unshift 向數組首位添加一個元素 3、pop 獲取數組中最後一個元素 4、slice 截取數組 5、splice 對數組的增刪改 ...
  • css 單位px、em、rem、vh、vw、vmin、vmax區別 ...
  • 一直自以為自己vue還可以,一直自以為webpack還可以,今天在慕課逛node的時候,才發現,自己還差的很遠。眾所周知,vue-cli基於webpack,而webpack基於node,對node不瞭解,談什麼瞭解webpack。所以就自己給自己出了一道題,爬取豆瓣數據,目前還處於初級階段。今天就淺 ...
  • 工作需要,項目中需要完成一個日誌工作安排的功能,網上找了好多資料,最後還是修修改改花了一些時間的 碼雲代碼地址:https://gitee.com/yinxiuli/fullcalendar_log_management.git 主要貼一下前端部分的代碼(可見git): <!DOCTYPE html ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...