摘要:環境變量法通過上一節的源碼分析,我們知道了的作用,那么如何使用或者優雅的使用來解決依賴加載問題呢嘗試一最為直接的是,修改系統的環境變量。
模塊加載痛點
大家也或多或少的了解node模塊的加載機制,最為粗淺的表述就是依次從當前目錄向上級查詢node_modules目錄,若發現依賴則加載。但是隨著應用規模的加大,目錄層級越來越深,若是在某個模塊中想要通過require方式以依賴名稱或相對路徑的方式引用其他模塊就非常麻煩,影響開發效率和美觀。
示例demo:
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules var gulp = require("../../lib/gulp"); gulp.task("say",function(){ console.log("hello wolrd"); });
目前的條件下,只有采用上述中相對路徑的方式引用依賴模塊,可以看出上述引用的缺點:
丑陋,十分繁雜
容易出錯,難以維護
第二個缺點是最難以接受的,在多次引用模塊的情況下問題會被放大,因此急需尋找某種方案解決多層目錄依賴引用,本文將會討論筆者在開發過程中的一些嘗試,并歡迎大家一起討論其他可行性方案。
全局變量法由于目標是解決毫無美觀又難以理解的相對目錄層級,那么可以嘗試使用變量完成目錄層級的替代。這種方案最為直接,且node加載該依賴的速度最快,無需遍歷其他各級目錄。但是為了更為通用,筆者常采用全局變量的方式綁定目錄關系:
demo:
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules global._root = "/usr/lib/node_modules"; var path = require("path"); var gulp = require(path.join(_root,"gulp")); ...
這種方案最為直接,但是可擴展性并不強,而且在多人維護的情況下尤甚,因此建議在單人開發的小項目中采用。
直接引用模塊名直接引用模塊名,說到底就是直接引用node_modules目錄中的依賴,類似引用node默認加載的那些模塊,如http,event模塊。
demo:
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules var gulp = require("gulp"); ...
在目錄/usr/local/test、/usr/local、/usr、/四個目錄下都沒有“node_modules”目錄或者“node_modules”目錄下都沒有gulp模塊,那么運行這個文件,肯定會報錯“MODULE_NOT_FOUND”,這就是我們接下來需要解決的問題,即如何修改node加載依賴的層級關系。
修改依賴加載層級相信大家學習node也都讀過一本書《深入淺出nodejs》,這本書的第二章第二節曾簡要介紹node加載依賴所遍歷的一些目錄,書中讓我們在某個測試文件中輸出module.paths,結果是一個數組,類似于
["/usr/local/test/node_modules"、"/usr/local/node_modules"、"/usr/node_modules"、"/node_modules"]
這給我們一個啟發,即加載某個模塊的順序就是按照上述數組項的順序依次判斷模塊是否存在,若存在則加載,事實上node也確實是這樣做的(下文會針對源碼分析猜想的正確性)。那么,在猜想的基礎上我們可以嘗試修改該數組下可否影響本模塊加載依賴的順序,如果成功自然美麗,如若不成功需尋找更為恰當的解決方案。
嘗試1:
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules module.paths.push("/usr/lib/node_modules"); console.log(module.paths); var gulp = require("gulp");
執行命令,一切正常,成功了。通過輸出信息可看出
["/usr/local/test/node_modules"、"/usr/local/node_modules"、"/usr/node_modules"、"/node_modules","/usr/lib/node_modules"]
確實修改了依賴查找層級,不過可以看出設置的目錄是在數組中的最后一位,這意味著node會在找到gulp依賴前遍歷4層目錄,最后才在第五層目錄中找到它。如果項目中只引用了gulp也還好,但是隨著其他依賴的數量增多,運行時加載依賴/usr/lib/node_modules下的依賴將會耗費不少時間。因此建議大家在項目中評估好依賴的位置,如果合適的話可以優先加載手動設置的依賴目錄:
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules module.paths.unshift("/usr/lib/node_modules"); console.log(module.paths); var gulp = require("gulp");
這樣,我們在不知道node底層如何工作的前提下就實現了目標。哈哈,不過作為一名靠譜的前端(node)工程師,我們不會滿足這種程度吧?哈哈!
深入源碼探究筆者摘出了與模塊(依賴)加載相關的代碼:
// 初始化全局的依賴加載路徑 Module._initPaths = function() { ... var paths = [path.resolve(process.execPath, "..", "..", "lib", "node")]; if (homeDir) { paths.unshift(path.resolve(homeDir, ".node_libraries")); paths.unshift(path.resolve(homeDir, ".node_modules")); } // 我們需要著重關注此處,獲取環境變量“NODE_PATH” var nodePath = process.env["NODE_PATH"]; if (nodePath) { paths = nodePath.split(path.delimiter).concat(paths); } // modulePaths記錄了全局加載依賴的根目錄,在Module._resolveLookupPaths中有使用 modulePaths = paths; // clone as a read-only copy, for introspection. Module.globalPaths = modulePaths.slice(0); }; // @params: request為加載的模塊名 // @params: parent為當前模塊(即加載依賴的模塊) Module._resolveLookupPaths = function(request, parent) { ... var start = request.substring(0, 2); // 若為引用模塊名的方式,即require("gulp") if (start !== "./" && start !== "..") { // 此處的modulePaths即為Module._initPaths函數中賦值的變量 var paths = modulePaths; if (parent) { if (!parent.paths) parent.paths = []; paths = parent.paths.concat(paths); } return [request, paths]; } // 使用eval執行可執行字符串的情況下,parent.id 和parent.filename為空 if (!parent || !parent.id || !parent.filename) { var mainPaths = ["."].concat(modulePaths); mainPaths = Module._nodeModulePaths(".").concat(mainPaths); return [request, mainPaths]; } ... };
Module._initPaths函數在默認的生命周期內只執行一次,作用自然是設置全局加載依賴的相對路徑。而當每次在文件中執行require加載其他依賴時,Module._resolveLookupPaths函數都會執行,返回一個包含依賴名和可遍歷的目錄數組(該數組中的目錄項可以加載到依賴,也可以無法加載依賴)。最后的工作就是根據Module._resolveLookupPaths函數返回的結果,遍歷目錄數組,加載依賴。如果遍歷結束后仍沒有找到依賴,則拋錯。
在分析完源碼后,相信大家也都注意了幾點信息:
Module._initPaths函數內部檢查了NODE_PATH環境變量
Module._initPaths函數只執行一次
Module._initPaths函數初始化的全局依賴加載路徑與module.paths有關系
那么,我們可以從另一個角度解決依賴加載的問題。
環境變量法通過上一節的源碼分析,我們知道了NODE_PATH的作用,那么如何使用或者優雅的使用NODE_PATH來解決依賴加載問題呢?
嘗試一最為直接的是,修改系統的環境變量。在linux下,執行
export NODE_PATH=/usr/lib/node_modules
即可解決。
但是,這種方案畢竟不優雅,因為我們的一個項目就修改了系統的環境變量,如果其他項目也采用這種方案,那么相信系統的NODE_PATH將會變得很長,而且會由于NODE_PATH的子路徑順序問題出現意想不到的沖突,因此作為這種解決方案不建議使用。
嘗試二我們希望只針對當前運行的程序設置環境變量,不影響其他程序;而且一旦當前程序退出,設置的環境變量也被恢復。滿足這種需求的實現,最為直觀的就是命令行配置。通過查閱node手冊可以這樣運行:
NODE_PATH=/usr/lib/node_modules node /usr/local/test/index.js
這樣,仍可以成功加載gulp依賴,而不影響系統的環境變量。
但是,命令行的方式顯而易見,就是丑陋,麻煩。每次運行程序都需要提前輸入一系列的路徑,這種方式將代碼的可維護性變為了程序的可維護性,在負責的項目中不適合使用。
嘗試三node運行時給我們提供了一個變量,對,就是process。process是node默認加載的Process模塊的一個屬性,通過process可獲取應用進程的相關信息,同時包括設置的環境變量。
我們可以在應用的入口文件設置環境變量:
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules process.env.NODE_PATH="/usr/lib/node_modules"; var gulp = require("gulp");
這樣我們在執行文件,意想不到的事情發生了,仍報出“MODULE_NOT_FOUND”錯誤。
這是為什么呢?原因仍要追溯到源碼。在源碼分析小節中總結了三點,其中第二點提到了Module._initPaths函數只執行一次,這意味著當我們在代碼中設置了process.env.NODE_PATH="/usr/lib/node_modules";,可是由于此時Module._initPaths已執行完畢,因此設置的環境變量并沒有被使用。解決這個問題也比較簡單,即重新調用Module._initPaths即可。
// 當前目錄: /usr/local/test/index.js // gulp模塊所在路徑為 /usr/lib/node_modules process.env.NODE_PATH="/usr/lib/node_modules"; require("module").Module._initPaths(); // 或者 module.constructor._initPaths() var gulp = require("gulp");
這樣,安全無公害的解決了多基目錄下依賴調用的問題。
總結本文從實際開發中遇到的問題出發,提出了幾種解決多基目錄下依賴的幾種方案:
全局變量法
修改module.paths方法
環境變量法(三種實現)
當然,社區還有一些幫助解決這種問題的模塊,如“app-module-path”,但思想也大同小異。在這里和大家一起分享學習收獲,希望對各位有些啟發和感悟,不勝感激!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80932.html
摘要:例如指定一些依賴到模塊中實現規范的模塊化,感興趣的可以查看的文檔。 CommonJS 定義了 module、exports 和 require 模塊規范,Node.js 為了實現這個簡單的標準,從底層 C/C++ 內建模塊到 JavaScript 核心模塊,從路徑分析、文件定位到編譯執行,經歷了一系列復雜的過程。簡單的了解 Node 模塊的原理,有利于我們重新認識基于 Node 搭建的...
摘要:可以通過配置屬性進行修改,默認將會自動創建個庫文件僅含有依然會創建個庫文件僅含有假設所有的體積都大于將會創建一個庫文件和一個通用組件文件僅含有當這些體積小于是,會故意將該模塊復制到三個文件中。 該文章內容大致翻譯自 webpack 4: Code Splitting, chunk graph and the splitChunks optimization 原有的問題 webpack...
摘要:我們可以為元素添加屬性然后在回調函數中接受該元素在樹中的句柄,該值會作為回調函數的第一個參數返回。使用最常見的用法就是傳入一個對象。單向數據流,比較有序,有便于管理,它隨著視圖庫的開發而被概念化。 面試中問框架,經常會問到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時卻答不上來,也是挺尷尬的,就干脆把react相關的問題查了下資料,再按自己的理解整理了下這些答案。 reac...
摘要:近期在閱讀最新幾版的官方文檔過程中發現不少術語不清之處特發此文總結以下的術語大量在官方文檔中直接出現且直接如基本詞語一樣使用不理解它們會嚴重影響閱讀自適應自旋鎖自適應自旋鎖是一個允許線程在特定點自旋等待特定事件發生而不是直接進行并等待該事件 近期在閱讀JAVA最新幾版的官方文檔過程中發現不少術語不清之處,特發此文總結.以下的術語大量在官方文檔中直接出現,且直接如基本詞語一樣使用,不理解...
摘要:為了防止某些文檔或腳本加載別的域下的未知內容,防止造成泄露隱私,破壞系統等行為發生。模式構建函數響應式前端架構過程中學到的經驗模式的不同之處在于,它主要專注于恰當地實現應用程序狀態突變。嚴重情況下,會造成惡意的流量劫持等問題。 今天是編輯周刊的日子。所以文章很多和周刊一樣。微信不能發鏈接,點了也木有用,所以請記得閱讀原文~ 發個動圖娛樂下: 使用 SVG 動畫制作游戲 使用 GASP ...
閱讀 3207·2021-11-19 09:40
閱讀 3005·2021-09-09 09:32
閱讀 792·2021-09-02 09:55
閱讀 1393·2019-08-26 13:23
閱讀 2403·2019-08-26 11:46
閱讀 1229·2019-08-26 10:19
閱讀 2054·2019-08-23 16:53
閱讀 1072·2019-08-23 12:44