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