Node.js의 모듈 경로 분석에 대해 이야기하는 기사
이 글은 Node.js의 모듈 경로 분석을 안내하고 Node 모듈 경로 분석 방법을 소개합니다. 도움이 되길 바랍니다!
require
사례
- 현재 프로젝트가 있습니다
- 현재 프로젝트 경로
/Users/ Rainbow /Documents/Front-end/Scaffolding Development/rainbow-test
- 프로젝트 bin 디렉토리에 많은 파일이 있습니다
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; };
for循环的核心算法解析:
Module._load
方法
Module._load(id, this, /* isMain */ false)
核心实现代码是:const filename = Module._resolveFilename(request, parent, isMain);
require.resolve
Node.js
项目模块路径解析是通过require.resolve
方式实现的。
- require.resolve就是通过
Module._resolveFileName
方法实现的,
// 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和环境中的路径结合起来 - 通过
Module._findPath
查询模块的真实路径
return Module._resolveFilename(request, parent, isMain);
- /bin/ index.js
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; // ... };
[ '/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모듈 실행 파일을 구문 분석하고 찾는 프로세스
require를 통해 구현됩니다. 해결
메소드. 🎜- require.resolve는
Module._resolveFileName
메소드를 통해 구현됩니다. Module._resolveFileName
핵심 프로세스는 다음과 같습니다.- < li>경로가 내장 모듈인지 확인
- 그렇지 않은 경우
Module._resolveLookupPahts
메서드를 사용하여 node_modules에 대한 가능한 경로를 생성합니다. test/lerna /cli.js', 각 경로 수준 아래에node_moduels
의 경로 배열을 추가합니다. Module._findPath</를 통해 모듈의 실제 경로를 쿼리합니다. code>, < /li></ul></li></ul>🎜2. <code>Module._findPath
핵심 프로세스는 다음과 같습니다. 🎜- 쿼리 캐시(요청 통과 및
x00
병합을 통해cacheKey
생성) Module._resolveLookupPahts
메서드에 의해 생성된 경로 배열을 탐색하고 비교합니다.request
가 있는path
는 파일 경로 basePath를 구성합니다.basePath
가 존재하는 경우fs.realPahtSync</code를 호출합니다. > 파일의 실제 경로를 얻으려면</li> <li>파일의 실제 경로를 <code>Module._pathCache
에 캐시하세요(키는 캐시키입니다)(Module._pathCache는 맵입니다)
fs.realPahtSync
핵심 프로세스: 🎜- 쿼리 캐시(캐시 키는 p입니다. Module._findPath에서 생성된 경로입니다)
- 경로 문자열을 왼쪽에서 오른쪽으로 순회하여 질의 / 언제, 경로를 분할하여 경로가 소프트 링크인지 확인하고, 실제 링크를 질의하여 새로운 경로 p를 생성한다. 자세한 내용은 다음과 같습니다.
- 순회 프로세스 중에 생성된 하위 경로 기반은 반복되는 쿼리를 피하기 위해 KnownHard 및 캐시에 캐시됩니다.
- 순회가 완료된 후, 이때 모듈에 해당하는 실제 경로가 키로 사용되고 실제 경로가 값으로 사용되어 캐시에 저장됩니다. >require.resolve.paths는
Module._resolveLookupPaths
와 동일합니다. 이 메서드는 node_modules의 가능한 모든 경로를 얻어 배열을 형성합니다. 🎜🎜5.require.resolve.paths
의 구현 원칙은 다음과 같습니다. 🎜/
(루트 경로)인 경우를 직접 반환합니다. ['/node_modules ']
- 그렇지 않으면 경로 문자열을 뒤에서 앞으로 이동합니다. /가 쿼리되면 경로를 분할하고 끝에 node_modules를 추가한 다음 경로 배열을 전달합니다. 쿼리는 더 이상 내장 모듈의 메서드를 사용하여
require(' yargs')
🎜 🎜🎜require 메소드🎜- 실제 용도는
모듈입니다. _load
메소드
rrree🎜🎜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._nodeModulePaths
메서드🎜🎜🎜rrreee🎜for 루프의 핵심 알고리즘 분석:🎜🎜
🎜🎜🎜
Module._load< /code>메서드</strong></span>🎜🎜<code>Module._load(id, this, /* isMain */ false)
🎜🎜핵심 구현 코드는 다음과 같습니다:const filename = Module ._resolveFilename(request, parent, isMain);
🎜🎜🎜require.resolve
🎜🎜Node.js
프로젝트 모듈 경로 확인은require.resolve
메서드를 통해 구현됩니다. 🎜- require.resolve는
Module._resolveFileName
메서드를 통해 구현됩니다.
Module._resolveFileName
핵심 프로세스🎜- 경로가 내장 모듈인지 확인
- 그렇지 않은 경우
Module._resolveLookupPahts
메소드는 경로를 환경의 경로와 결합합니다. Module._findPath
를 통해 모듈의 실제 경로를 쿼리합니다.
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 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











이 기사는 NodeJS V8 엔진의 메모리 및 가비지 수집기(GC)에 대한 심층적인 이해를 제공할 것입니다. 도움이 되기를 바랍니다.

Non-Blocking, Event-Driven 기반으로 구축된 Node 서비스는 메모리 소모가 적다는 장점이 있으며, 대규모 네트워크 요청을 처리하는데 매우 적합합니다. 대규모 요청을 전제로 '메모리 제어'와 관련된 문제를 고려해야 합니다. 1. V8의 가비지 수집 메커니즘과 메모리 제한 Js는 가비지 수집 기계에 의해 제어됩니다.

Node용 Docker 이미지를 선택하는 것은 사소한 문제처럼 보일 수 있지만 이미지의 크기와 잠재적인 취약점은 CI/CD 프로세스와 보안에 상당한 영향을 미칠 수 있습니다. 그렇다면 최고의 Node.js Docker 이미지를 어떻게 선택합니까?

파일 모듈은 파일 읽기/쓰기/열기/닫기/삭제 추가 등과 같은 기본 파일 작업을 캡슐화한 것입니다. 파일 모듈의 가장 큰 특징은 모든 메소드가 **동기** 및 ** 두 가지 버전을 제공한다는 것입니다. 비동기**, sync 접미사가 있는 메서드는 모두 동기화 메서드이고, 없는 메서드는 모두 이기종 메서드입니다.

Node 19가 정식 출시되었습니다. 이 글에서는 Node.js 19의 6가지 주요 기능에 대해 자세히 설명하겠습니다. 도움이 되셨으면 좋겠습니다!

Node.js는 GC(가비지 수집)를 어떻게 수행하나요? 다음 기사에서는 이에 대해 설명합니다.

이벤트 루프는 Node.js의 기본 부분이며 메인 스레드가 차단되지 않도록 하여 비동기 프로그래밍을 가능하게 합니다. 이벤트 루프를 이해하는 것은 효율적인 애플리케이션을 구축하는 데 중요합니다. 다음 기사는 Node.js의 이벤트 루프에 대한 심층적인 이해를 제공할 것입니다. 도움이 되기를 바랍니다!

nodejs 실행 파일을 pkg로 패키징하는 방법은 무엇입니까? 다음 기사에서는 pkg를 사용하여 Node 프로젝트를 실행 파일로 패키징하는 방법을 소개합니다. 도움이 되기를 바랍니다.
