一文聊聊Node.js中的模組路徑解析
本篇文章帶大家了解Node.js中的模組路徑解析,介紹一下Node模組路徑解析方法,希望對大家有幫助!
require
案例
- #目前有一個項目
- 目前項目路徑
/Users/rainbow/Documents/前端/鷹架開發/rainbow-test
- #專案bin目錄下方有一堆檔案
- #/bin/index.js
console.log(require.resolve(".")); // /Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin/index.js 输出bin/index.js的绝对路径 console.log(require.resolve.paths(".")); // [ '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin' ] 输出的文件可能在的路径的数组
console.log(require.resolve("yargs")); // /Users/rainbow/Documents/前端/脚手架开发/rainbow-test/node_modules/yargs/index.cjs console.log(require.resolve.paths("yargs")); /* [ '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin/node_modules', '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/node_modules', '/Users/rainbow/Documents/前端/脚手架开发/node_modules', '/Users/rainbow/Documents/前端/node_modules', '/Users/rainbow/Documents/node_modules', '/Users/rainbow/node_modules', '/Users/node_modules', '/node_modules', '/Users/rainbow/.node_modules', '/Users/rainbow/.node_libraries', '/usr/local/Cellar/node/14.3.0_1/lib/node' ] */
require解析並找到模組執行檔的流程
1、Nodejs專案模組路徑解析是透過require.resolve
方式實現的。
- require.resolve就是透過
Module._resolveFileName
方法實作的 Module._resolveFileName
#核心流程是:- #判斷路徑是否為內建模組
- 不是,則透過
Module._resolveLookupPahts
方法,產生node_modules可能存在的路徑,如果傳入的路徑是'/test/lerna/cli. js',在每一級路徑下加上node_moduels
的路徑數組 - #透過
Module._findPath
查詢模組的真實路徑,
#2、Module._findPath
核心流程是:
- 查詢快取(將request和paths透過
\x00
合併生成cacheKey
) - 遍歷
Module._resolveLookupPahts
方法產生的paths數組,將path
與request
組成檔案路徑basePath - 如果
basePath
存在則呼叫fs.realPahtSync
取得檔案的真實路徑 - 將檔案真實路徑快取到
Module._pathCache
(key為cacheKey)(Module._pathCache就是一個map)
#3、fs.realPahtSync
核心流程:
- #查詢快取(快取的key為p。即Module._findPath中產生的路徑)
- 從左往右遍歷路徑字串,查詢到/時,拆分路徑,判斷該路徑是否為軟鏈接,如果是軟連結則查詢真實鏈接,並產生新路徑p,然後繼續讓後遍歷,這裡有一個細節:
- 遍歷過程中產生的子路徑base會快取在knownHard和cache中,避免重複查詢
- 遍歷完成得到模組對應的真實路徑,此時會將原始路徑original作為key,真實路徑作為value,保存到快取中
4、require.resolve. paths
等價於Module._resolveLookupPaths
,此方法取得所有node_modules可能存在的路徑組成一個陣列。
5、require.resolve.paths
實作原理是:
- 如果是
/
(根路徑)直接回傳['/node_modules']
- 否則,將路徑字串從後往前遍歷,查詢到/時,拆分路徑,在後面加上node_modules,並傳入一個paths數組,直到查詢不到/後回傳paths陣列
require使用到內建模組的方法
當我們使用require('yargs')
時
require方法
- #實際上使用的是
Module._load
方法
Module.prototype.require = function(id) { //id = 'yargs' validateString(id, 'id'); if (id === '') { throw new ERR_INVALID_ARG_VALUE('id', id, 'must be a non-empty string'); } requireDepth++; try { return Module._load(id, this, /* isMain */ false); } finally { requireDepth--; } };
// 参数 id = 'yargs' this={ paths: Module._nodeModulePaths(process.cwd()) }
Module._nodeModulePaths
方法
##
// 进入mac电脑所在的逻辑: // from => /Users/rainbow/Documents/前端/脚手架开发/lerna源码/lernas //'from' is the __dirname of the module. Module._nodeModulePaths = function(from) { from = path.resolve(from); // Return early not only to avoid unnecessary work, but to *avoid* returning // an array of two items for a root: [ '//node_modules', '/node_modules' ] if (from === '/') return ['/node_modules']; const paths = []; // 关键算法代码 for (let i = from.length - 1, p = 0, last = from.length; i >= 0; --i) { const code = from.charCodeAt(i); if (code === CHAR_FORWARD_SLASH) { if (p !== nmLen) paths.push(from.slice(0, last) + '/node_modules'); last = i; p = 0; } else if (p !== -1) { if (nmChars[p] === code) { ++p; } else { p = -1; } } } // Append /node_modules to handle root paths. paths.push('/node_modules'); return paths; };
#Module._load方法
Module._load(id, this, /* isMain */ false)
核心實作碼是:
const filename = Module._resolveFilename(request, parent, isMain);
##require.resolve
Node.js
- require.resolve
- 方式實現的。
方法實現的,// node.js内置模块require的源代码
function resolve(request, options) {
validateString(request, 'request');
return Module._resolveFilename(request, mod, false, options); //核心实现
}
require.resolve = resolve;
function paths(request) {
validateString(request, 'request');
return Module._resolveLookupPaths(request, mod); //核心代码
}
resolve.paths = paths;
Module._resolveFileName
判斷路徑是否為內建模組
#不是,則透過- Module._resolveLookupPahts
方法,將paths和環境中的路徑結合起來
透過
查詢模組的真實路徑
return Module._resolveFilename(request, parent, isMain) ;
Module._resolveFilename = function(request, parent, isMain, options) { if (NativeModule.canBeRequiredByUsers(request)) { //是否为内置模块 return request; } let paths; // 让paths和环境变量中的paths结合 paths = Module._resolveLookupPaths(request, parent); //核心代码 if (parent && parent.filename) { // 读取filename对应的package.json文件,看是否有exports字段,当前filename = false const filename = trySelf(parent.filename, request); if (filename) { //false const cacheKey = request + '\x00' + (paths.length === 1 ? paths[0] : paths.join('\x00')); Module._pathCache[cacheKey] = filename; return filename; } } //关键代码,找到本地执行文件 // Look up the filename first, since that's the cache key. const filename = Module._findPath(request, paths, isMain, false); if (filename) return filename; // ... };
Module._resolveLookupPahts
方法
- 生成要查找模块的所有路径上可能存在node_modules的路径数组
require.resolve.paths("yargs")
核心实现方法
生成
[ '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/bin/node_modules', '/Users/rainbow/Documents/前端/脚手架开发/rainbow-test/node_modules', '/Users/rainbow/Documents/前端/脚手架开发/node_modules', '/Users/rainbow/Documents/前端/node_modules', '/Users/rainbow/Documents/node_modules', '/Users/rainbow/node_modules', '/Users/node_modules', '/node_modules', '/Users/rainbow/.node_modules', '/Users/rainbow/.node_libraries', '/usr/local/Cellar/node/14.3.0_1/lib/node' ]
Module._resolveLookupPaths = function(request, parent) { if (NativeModule.canBeRequiredByUsers(request)) { debug('looking for %j in []', request); return null; } // Check for node modules paths. if (request.charAt(0) !== '.' || (request.length > 1 && request.charAt(1) !== '.' && request.charAt(1) !== '/' && (!isWindows || request.charAt(1) !== '\'))){ let paths = modulePaths; if (parent != null && parent.paths && parent.paths.length) { paths = parent.paths.concat(paths); } debug('looking for %j in %j', request, paths); return paths.length > 0 ? paths : null; } // In REPL, parent.filename is null. if (!parent || !parent.id || !parent.filename) { // Make require('./path/to/foo') work - normally the path is taken // from realpath(__filename) but in REPL there is no filename const mainPaths = ['.']; debug('looking for %j in %j', request, mainPaths); return mainPaths; } debug('RELATIVE: requested: %s from parent.id %s', request, parent.id); const parentDir = [path.dirname(parent.filename)]; debug('looking for %j', parentDir); return parentDir; };
Module._findPath
核心流程
- 查询缓存(将request和paths通过
\x00
合并生成cacheKey
)(\x00
是空格的16进制) - 遍历
Module._resolveLookupPahts
方法生成的paths
数组,将path
与request
组成文件路径basePath
- 如果basePath存在则调用
fs.realPahtSync
获取文件的真实路径
fs.realPahtSync
更多node相关知识,请访问:nodejs 教程!!
以上是一文聊聊Node.js中的模組路徑解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

基於無阻塞、事件驅動建立的Node服務,具有記憶體消耗低的優點,非常適合處理海量的網路請求。在海量請求的前提下,就需要考慮「記憶體控制」的相關問題了。 1. V8的垃圾回收機制與記憶體限制 Js由垃圾回收機

選擇一個Node的Docker映像看起來像是小事,但是映像的大小和潛在漏洞可能會對你的CI/CD流程和安全造成重大的影響。那我們要如何選擇一個最好Node.js Docker映像呢?

Node 19已正式發布,以下這篇文章就來帶大家詳解了解Node.js 19的 6 大特性,希望對大家有幫助!

文件模組是對底層文件操作的封裝,例如文件讀寫/打開關閉/刪除添加等等文件模組最大的特點就是所有的方法都提供的**同步**和**異步**兩個版本,具有sync 字尾的方法都是同步方法,沒有的都是異

事件循環是 Node.js 的基本組成部分,透過確保主執行緒不被阻塞來實現非同步編程,了解事件循環對建立高效應用程式至關重要。以下這篇文章就來帶大家深入了解Node中的事件循環 ,希望對大家有幫助!

如何用pkg打包nodejs可執行檔?以下這篇文章跟大家介紹一下使用pkg將Node專案打包為執行檔的方法,希望對大家有幫助!
