十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
這篇文章主要介紹“Node.js中的模塊路徑是怎樣的”,在日常操作中,相信很多人在Node.js中的模塊路徑是怎樣的問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”Node.js中的模塊路徑是怎樣的”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)網(wǎng)站建設(shè)提供從項(xiàng)目策劃、軟件開發(fā),軟件安全維護(hù)、網(wǎng)站優(yōu)化(SEO)、網(wǎng)站分析、效果評(píng)估等整套的建站服務(wù),主營業(yè)務(wù)為網(wǎng)站建設(shè)、成都網(wǎng)站制作,app軟件開發(fā)公司以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。成都創(chuàng)新互聯(lián)深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

require案例當(dāng)前有一個(gè)項(xiàng)目
當(dāng)前項(xiàng)目路徑/Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test
項(xiàng)目bin目錄下有一堆文件

/bin/index.js
console.log(require.resolve("."));
// /Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/bin/index.js 輸出bin/index.js的絕對(duì)路徑
console.log(require.resolve.paths("."));
// [ '/Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/bin' ] 輸出的文件可能在的路徑的數(shù)組console.log(require.resolve("yargs"));
// /Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/node_modules/yargs/index.cjs
console.log(require.resolve.paths("yargs"));
/*
[
'/Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/bin/node_modules',
'/Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/node_modules',
'/Users/rainbow/Documents/前端/腳手架開發(fā)/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'
]
*/1、Nodejs項(xiàng)目模塊路徑解析是通過require.resolve方式實(shí)現(xiàn)的。
require.resolve就是通過Module._resolveFileName方法實(shí)現(xiàn)的
Module._resolveFileName核心流程是:
判斷該路徑是否是內(nèi)置模塊
不是,則通過Module._resolveLookupPahts方法,生成node_modules可能存在的路徑,如果傳入的路徑是’/test/lerna/cli.js’,在每一級(jí)路徑下加上node_moduels的路徑數(shù)組
通過Module._findPath查詢模塊的真實(shí)路徑,
2、Module._findPath核心流程是:
查詢緩存(將request和paths通過\x00合并生成cacheKey)
遍歷 Module._resolveLookupPahts方法生成的paths數(shù)組,將path與request組成文件路徑basePath
如果basePath存在則調(diào)用fs.realPahtSync獲取文件的真實(shí)路徑
將文件真實(shí)路徑緩存到Module._pathCache(key為cacheKey)(Module._pathCache就是一個(gè)map)
3、fs.realPahtSync核心流程:
查詢緩存(緩存的key為p。即Module._findPath中生成的路徑)
從左往右遍歷路徑字符串,查詢到/時(shí),拆分路徑,判斷該路徑是否為軟鏈接,如果是軟鏈接則查詢真實(shí)鏈接,并生成新路徑p,然后繼續(xù)讓后遍歷,這里有一個(gè)細(xì)節(jié):
遍歷過程中生成的子路徑base會(huì)緩存在knownHard和cache中,避免重復(fù)查詢
遍歷完成得到模塊對(duì)應(yīng)的真實(shí)路徑,此時(shí)會(huì)將原始路徑original作為key,真實(shí)路徑作為value,保存到緩存中
4、require.resolve.paths等價(jià)于Module._resolveLookupPaths,該方法獲取所有node_modules可能存在的路徑組成一個(gè)數(shù)組。
5、require.resolve.paths實(shí)現(xiàn)原理是:
如果是/(根路徑)直接返回['/node_modules']
否則,將路徑字符串從后往前遍歷,查詢到/時(shí),拆分路徑,在后面加上node_modules,并傳入一個(gè)paths數(shù)組,直到查詢不到/后返回paths數(shù)組
當(dāng)我們使用require('yargs')時(shí)
require方法
實(shí)際使用的是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--;
}
};// 參數(shù)
id = 'yargs'
this={
paths: Module._nodeModulePaths(process.cwd())
}Module._nodeModulePaths方法

// 進(jìn)入mac電腦所在的邏輯:
// from => /Users/rainbow/Documents/前端/腳手架開發(fā)/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 = [];
// 關(guān)鍵算法代碼
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;
};for循環(huán)的核心算法解析:

Module._load方法
Module._load(id, this, /* isMain */ false)
核心實(shí)現(xiàn)代碼是:const filename = Module._resolveFilename(request, parent, isMain);
require.resolve
Node.js項(xiàng)目模塊路徑解析是通過require.resolve方式實(shí)現(xiàn)的。
require.resolve就是通過Module._resolveFileName方法實(shí)現(xiàn)的,
// node.js內(nèi)置模塊require的源代碼
function resolve(request, options) {
validateString(request, 'request');
return Module._resolveFilename(request, mod, false, options); //核心實(shí)現(xiàn)
}
require.resolve = resolve;
function paths(request) {
validateString(request, 'request');
return Module._resolveLookupPaths(request, mod); //核心代碼
}
resolve.paths = paths;Module._resolveFileName核心流程
判斷該路徑是否是內(nèi)置模塊
不是,則通過Module._resolveLookupPahts方法,將paths和環(huán)境中的路徑結(jié)合起來
通過Module._findPath查詢模塊的真實(shí)路徑
return Module._resolveFilename(request, parent, isMain);

Module._resolveFilename = function(request, parent, isMain, options) {
if (NativeModule.canBeRequiredByUsers(request)) { //是否為內(nèi)置模塊
return request;
}
let paths;
// 讓paths和環(huán)境變量中的paths結(jié)合
paths = Module._resolveLookupPaths(request, parent); //核心代碼
if (parent && parent.filename) {
// 讀取filename對(duì)應(yīng)的package.json文件,看是否有exports字段,當(dāng)前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;
}
}
//關(guān)鍵代碼,找到本地執(zhí)行文件 // 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的路徑數(shù)組
require.resolve.paths("yargs")核心實(shí)現(xiàn)方法
生成
[ '/Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/bin/node_modules', '/Users/rainbow/Documents/前端/腳手架開發(fā)/rainbow-test/node_modules', '/Users/rainbow/Documents/前端/腳手架開發(fā)/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進(jìn)制)
遍歷Module._resolveLookupPahts方法生成的paths數(shù)組,將path與request組成文件路徑basePath
如果basePath存在則調(diào)用fs.realPahtSync獲取文件的真實(shí)路徑

fs.realPahtSync

到此,關(guān)于“Node.js中的模塊路徑是怎樣的”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!