일상적인 개발을 위해 Nodejs를 사용할 때 종종 두 가지 유형의 모듈을 가져오기 위해 require를 사용합니다. 하나는 우리가 직접 작성한 모듈이거나 npm을 사용하여 설치된 타사文件模块
입니다. os
, fs
및 기타 모듈과 같이 우리가 사용할 수 있도록 제공되는 Node의 내장 모듈입니다. 이러한 모듈을核心模块
이라고 합니다.
파일 모듈과 코어 모듈의 차이점은 Node에 내장되어 있는지 여부뿐 아니라 파일 위치, 모듈의 컴파일 및 실행 프로세스에도 있다는 점에 유의해야 합니다. 둘 사이에는 분명한 차이가 있습니다. . 뿐만 아니라 파일 모듈은 일반 파일 모듈, 사용자 정의 모듈 또는 C/C++ 확장 모듈 등으로 세분화될 수도 있습니다. 또한 모듈마다 파일 위치 지정, 컴파일 및 기타 프로세스가 다른 많은 세부 정보가 있습니다.
이 기사에서는 이러한 문제를 다루고 파일 모듈과 핵심 모듈의 개념은 물론 파일 위치, 컴파일 또는 실행 시 주의해야 할 특정 프로세스와 세부 사항을 명확히 설명합니다.
파일 모듈부터 시작해 보겠습니다.
파일 모듈이란 무엇입니까?
Node에서 .、.. 或/
로 시작하는 모듈 식별자(즉, 상대 경로 또는 절대 경로 사용)를 사용하여 필요한 모듈은 파일 모듈로 처리됩니다. 또한, 상대 경로나 절대 경로를 포함하지 않고 핵심 모듈도 아니지만 특별한 유형의 모듈이 있습니다. 노드는 이러한 유형의 모듈을 찾을 때模块路径
사용합니다. 모듈을 하나씩 검색하는模块路径
. 이러한 유형의 모듈을 사용자 정의 모듈이라고 합니다.
따라서 파일 모듈에는 두 가지 유형이 포함됩니다. 하나는 경로가 있는 일반 파일 모듈이고 다른 하나는 경로가 없는 사용자 정의 모듈입니다.
파일 모듈은 런타임 시 동적으로 로드되므로 완전한 파일 위치, 컴파일 및 실행 프로세스가 필요하며 코어 모듈보다 속도가 느립니다.
파일 위치 지정의 경우 Node는 이 두 가지 유형의 파일 모듈을 다르게 처리합니다. 이 두 가지 유형의 파일 모듈에 대한 검색 프로세스를 자세히 살펴보겠습니다.
일반 파일 모듈의 경우 전달 경로가 매우 명확하므로 검색 시간이 오래 걸리지 않으므로 아래에 소개된 사용자 정의 모듈보다 검색 효율성이 높습니다. 그러나 여전히 주목해야 할 두 가지 사항이 있습니다.
첫째, 일반적인 상황에서 파일 모듈을 도입하기 위해 require를 사용할 때 파일 확장자는 일반적으로 지정되지 않습니다. 예:
const math = require("math");
확장자가 지정되지 않았기 때문에 Node는 최종 파일을 결정할 수 없습니다. 이 경우 Node는 .js、.json、.node
순으로 확장자를 완성하고 하나씩 시도하게 됩니다. 이 과정을文件扩展名分析
이라고 합니다.
또한 실제 개발에서는 특정 파일을 요구하는 것 외에도 일반적으로 다음과 같은 디렉토리도 지정한다는 점에 유의해야 합니다.
const axios = require("../network");
이 경우 노드는 먼저 파일을 수행합니다. 확장 분석. 해당 파일을 찾을 수 없지만 디렉터리를 얻은 경우 Node는 해당 디렉터리를 패키지로 처리합니다.
구체적으로 Node는 디렉터리에 있는 package.json
의 main
필드가 가리키는 파일을 검색 결과로 반환합니다. main이 가리키는 파일이 잘못되었거나 package.json
파일이 전혀 존재하지 않는 경우 Node는 index
기본 파일 이름으로 사용한 다음 .js
및 .node
사용하여 확장 분석을 수행하고 대상 파일을 검색합니다. 하나씩 찾지 못하면 오류가 발생합니다.
(물론 Node에는 CJS와 ESM이라는 두 종류의 모듈 시스템이 있기 때문에 Node에서는 기본 필드를 검색하는 것 외에 다른 방법도 사용하게 됩니다. 이는 이 글의 범위를 벗어나므로 자세한 내용은 다루지 않겠습니다. )
방금언급했는데, 노드가 커스텀 모듈을 검색할 때 모듈 경로를 사용하게 됩니다.
모듈 파싱에 익숙한 친구들은 모듈 경로가 경로들로 구성된 배열이라는 것을 알아야 합니다. 구체적인 값은 다음 예제에서 볼 수 있습니다:
// example.js console.log(module.paths);
결과 출력:
보시다시피 Node의 모듈에는 module.paths
에 저장되고 Node가 현재 모듈에서 참조하는 사용자 정의 모듈을 찾는 방법을 지정하는 데 사용되는 모듈 경로 배열이 있습니다.
구체적으로 Node는 모듈 경로 배열을 순회하여 각 경로를 하나씩 시도하고 해당 경로에 해당하는 node_modules
디렉터리에 지정된 사용자 정의 모듈이 있는지 확인합니다. 그렇지 않으면 해당 경로에 도달할 때까지 단계별로 위쪽으로 반복됩니다. 루트 디렉터리의 node_modules
디렉터리. 대상 모듈을 찾을 때까지 찾을 수 없으면 오류가 발생합니다.
node_modules
디렉터리를 단계별로 재귀적으로 검색하는 것은 사용자 정의 모듈을 찾기 위한 Node의 전략이며, 모듈 경로는 이 전략의 구체적인 구현임을 알 수 있습니다.
동시에 우리는 사용자 정의 모듈을 검색할 때 레벨이 깊을수록 해당 검색에 더 많은 시간이 소요된다는 결론에 도달했습니다. 따라서 핵심 모듈 및 일반 파일 모듈에 비해 사용자 정의 모듈의 로딩 속도가 가장 느립니다.
물론, 모듈 경로를 기준으로 찾아낸 것은 특정 파일이 아닌 디렉터리일 뿐입니다. 디렉터리를 찾은 후 Node도 위에서 설명한 패키지 처리 프로세스에 따라 검색합니다. 구체적인 프로세스는 다시 설명하지 않습니다.
위는 일반 파일 모듈과 커스텀 모듈에 대해 주의해야 할 파일 위치 지정 프로세스 및 세부 사항입니다. 다음으로 두 가지 유형의 모듈이 어떻게 컴파일되고 실행되는지 살펴보겠습니다.
require가 가리키는 파일을 찾으면 일반적으로 모듈 식별자에는 확장자가 없습니다. 위에서 언급한 파일 확장자 분석에 따르면 Node는 다음과 같은 파일의 컴파일 및 실행을 지원한다는 것을 알 수 있습니다. 세 가지 확장명. :
JavaScript 파일. 파일은 fs
모듈을 통해 동기적으로 읽힌 다음 컴파일되고 실행됩니다. .node
및 .json
파일을 제외한 다른 파일은 .js
파일로 로드됩니다.
.node
파일은 C/C++로 작성한 후 컴파일되고 생성된 확장 파일입니다. Node는 process.dlopen()
메서드를 통해 파일을 로드합니다.
json 파일을 fs
모듈을 통해 동기적으로 읽은 후 JSON.parse()
사용하여 구문 분석하고 결과를 반환합니다.
파일 모듈을 컴파일하고 실행하기 전에 Node는 아래와 같이 모듈 래퍼를 사용하여 이를 래핑합니다.
(function(exports, require, module, __filename, __dirname) { //모듈 코드});
Node는 모듈 래퍼를 통해 모듈을 함수 범위로 패키징하고 다른 범위와 격리하여 변수 이름 충돌 및 전역 범위 오염과 같은 문제를 방지하는 것을 볼 수 있습니다. 내보내기 및 필수 매개변수를 전달하면 모듈이 필요한 가져오기 및 내보내기 기능을 가질 수 있습니다. 이것은 Node의 모듈 구현입니다.
모듈 래퍼를 이해한 후 먼저 json 파일의 컴파일 및 실행 과정을 살펴보겠습니다.
json 파일의 컴파일 및 실행이 가장 간단합니다. fs
모듈을 통해 JSON 파일의 내용을 동기적으로 읽은 후 Node는 JSON.parse()를 사용하여 JavaScript 객체를 구문 분석한 다음 이를 모듈의 내보내기 객체에 할당하고 마지막으로 이를 참조하는 모듈에 반환합니다. .과정은 매우 간단하고 조악합니다.
모듈 래퍼를 사용하여 JavaScript 파일을 래핑한 후 래핑된 코드는 vm
모듈의 runInThisContext()
(eval과 유사) 메서드를 통해 실행되어 함수 개체를 반환합니다.
그런 다음 JavaScript 모듈의 내보내기, 요구 사항, 모듈 및 기타 매개변수가 실행을 위해 이 함수에 전달됩니다. 실행 후 모듈의 내보내기 속성이 호출자에게 반환됩니다. 이는 JavaScript 파일의 컴파일 및 실행 프로세스입니다.
C/C++ 확장 모듈의 컴파일 및 실행을 설명하기 전에 먼저 C/C++ 확장 모듈이 무엇인지 소개하겠습니다.
C/C++ 확장 모듈은 이름에서 알 수 있듯이 C/C++로 작성되었으며, 로드된 후 컴파일할 필요가 없다는 점입니다. 직접 실행된 후 JavaScript 모듈보다 약간 빠르게 로드됩니다. JS로 작성된 파일 모듈과 비교할 때 C/C++ 확장 모듈은 분명한 성능 이점을 가지고 있습니다. Node 코어 모듈에서 처리할 수 없거나 특정 성능 요구 사항이 있는 기능의 경우 사용자는 C/C++ 확장 모듈을 작성하여 목표를 달성할 수 있습니다.
그렇다면 .node
파일은 무엇이며 C/C++ 확장 모듈과 어떤 관련이 있습니까?
실제로 작성된 C/C++ 확장 모듈이 컴파일되면 .node
파일이 생성됩니다. 즉, 모듈의 사용자로서 C/C++ 확장 모듈의 소스 코드를 직접 소개하는 것이 아니라, C/C++ 확장 모듈의 컴파일된 바이너리 파일을 소개하는 것입니다. 따라서 .node
파일은 컴파일할 필요가 없습니다. Node가 .node
파일을 찾은 후에는 파일을 로드하고 실행하기만 하면 됩니다. 실행 중에 모듈의 내보내기 개체가 채워지고 호출자에게 반환됩니다.
C/C++ 확장 모듈을 컴파일하여 생성된 .node
파일은 다양한 플랫폼에서 다양한 형태를 갖는다는 점은 주목할 가치가 있습니다. *nix
시스템에서 C/C++ 확장 모듈은 g++/gcc와 같은 컴파일러에 의해 동적 링크 공유 개체 파일로 컴파일됩니다. 확장자는 .so
입니다. Windows
에서는 Visual C++ 컴파일러에 의해 동적 링크 라이브러리 파일로 컴파일되며 확장자는 .dll
입니다. 하지만 실제 사용하는 확장자는 .node
입니다. 사실 .node
확장자는 좀 더 자연스럽게 보이도록 하기 위한 것입니다. 사실 Windows
에서는 .dll 파일이고, *nix
.so
파일에서는 .dll
파일입니다. .
Node는 필요한 .node
파일을 찾은 후 process.dlopen()
메서드를 호출하여 파일을 로드하고 실행합니다. .node
파일은 플랫폼마다 파일 형식이 다르기 때문에 크로스 플랫폼 구현을 달성하기 위해 dlopen()
메서드는 Windows
및 *nix
플랫폼에서 서로 다른 구현을 가지며 libuv
호환성 계층을 통해 캡슐화됩니다. 다음 그림은 다양한 플랫폼에서 C/C++ 확장 모듈의 컴파일 및 로드 프로세스를 보여줍니다.
코어 모듈은 Node 소스 코드를 컴파일하는 동안 바이너리 실행 파일로 컴파일됩니다. Node 프로세스가 시작되면 일부 핵심 모듈이 메모리에 직접 로드되므로 이러한 핵심 모듈이 도입되면 파일 위치와 컴파일 및 실행의 두 단계가 생략될 수 있으며 경로에서 파일 모듈보다 먼저 판단됩니다. 분석 결과 로딩 속도가 가장 빠릅니다.
실제로 핵심 모듈은 C/C++와 JavaScript로 작성된 두 부분으로 나누어져 있습니다. C/C++ 파일은 Node 프로젝트의 src 디렉터리에 저장되고, JavaScript 파일은 lib 디렉터리에 저장됩니다. 분명히 이 두 모듈 부분의 컴파일 및 실행 프로세스는 다릅니다.
JavaScript 코어 모듈을 컴파일하기 위해 Node 소스 코드를 컴파일하는 동안 Node는 V8과 함께 제공되는 js2c.py 도구를 사용하여 JavaScript 코어 모듈을 포함한 모든 내장 JavaScript 코드를 변환합니다. C++에서 JavaScript 코드는 노드 네임스페이스에 문자열로 저장됩니다. Node 프로세스를 시작하면 JavaScript 코드가 메모리에 직접 로드됩니다.
JavaScript 핵심 모듈이 도입되면 Node는 process.binding()
호출하여 모듈 식별자 분석을 통해 메모리에서 해당 위치를 찾고 검색합니다. 꺼낸 후 JavaScript 핵심 모듈도 모듈 래퍼로 래핑된 다음 실행되고 내보내기 개체가 내보내지고 호출자에게 반환됩니다.
핵심 모듈에서 컴파일되고 실행됩니다. 일부 모듈은 모두 C/C++로 작성되고, 일부 모듈에는 C/C++로 완성된 핵심 부분이 있으며, 다른 부분은 성능 요구 사항을 충족하기 위해 JavaScript로 패키지되거나 내보내집니다. buffer
, fs
, os
등과 같은 모듈은 부분적으로 C/C++로 작성되었습니다. C++ 모듈이 메인 파트 내부에 코어를 구현하고 JavaScript 모듈이 메인 파트 외부에 캡슐화를 구현하는 이 모델은 Node가 성능을 향상시키는 일반적인 방법입니다.
순수 C/C++로 작성된 핵심 모듈 부분을 node_fs
, node_os
등과 같은 내장 모듈이라고 합니다. 일반적으로 사용자가 직접 호출하지 않고 JavaScript 핵심 모듈에 직접적으로 의존합니다. 따라서 Node의 핵심 모듈 도입 과정에는 다음과 같은 참조 체인이 있습니다.
그렇다면 JavaScript 핵심 모듈은 내장 모듈을 어떻게 로드합니까?
process.binding()
메소드를 기억하시나요? Node는 이 메소드를 호출하여 메모리에서 JavaScript 핵심 모듈을 제거합니다. 이 방법은 내장 모듈 로드를 돕기 위해 JavaScript 핵심 모듈에도 적용됩니다.
이 메서드의 구현에 따라 내장 모듈을 로드할 때 먼저 빈 내보내기 개체를 만든 다음 get_builtin_module()
메서드를 호출하여 내장 모듈 개체를 꺼내고, register_func()
실행하여 내보내기 개체를 채웁니다. 마지막으로 호출자에게 반환하여 내보내기를 완료합니다. 내장 모듈을 로딩하고 실행하는 과정입니다.
위의 분석을 통해 코어 모듈과 같은 참조 체인을 도입하기 위한 os 모듈을 예로 들면 일반적인 프로세스는 다음과 같습니다.
정리하자면, os 모듈을 도입하는 과정은 자바스크립트 파일 모듈의 도입, 자바스크립트 코어 모듈의 로딩 및 실행, 내장 모듈의 로딩 및 실행으로 이루어지며, 그 과정은 매우 번거롭고 복잡하지만, 모듈 호출자의 경우 기본 차폐로 인해 복잡한 구현 및 세부 사항의 경우 전체 모듈을 require()를 통해 간단히 가져올 수 있는데 이는 매우 간단합니다. 친숙한.
이 글에서는 파일 모듈과 핵심 모듈의 기본 개념과 파일 위치, 컴파일, 실행 시 주의해야 할 특정 프로세스와 세부 사항을 소개합니다. 구체적으로,
파일 모듈은 다양한 파일 위치 지정 프로세스에 따라 일반 파일 모듈과 사용자 정의 모듈로 나눌 수 있습니다. 일반 파일 모듈은 명확한 경로로 인해 직접 찾을 수 있으며 때로는 파일 확장자 분석 및 디렉터리 분석 프로세스가 포함됩니다. 사용자 정의 모듈은 모듈 경로를 기반으로 검색하고 성공적인 검색 후 디렉터리 분석을 통해 최종 파일 위치가 수행됩니다. .
파일 모듈은 다양한 컴파일 및 실행 프로세스에 따라 JavaScript 모듈과 C/C++ 확장 모듈로 나눌 수 있습니다. JavaScript 모듈은 모듈 래퍼로 패키징된 후 vm
모듈의 runInThisContext
메서드를 통해 실행됩니다. C/C++ 확장 모듈은 이미 컴파일 후 생성된 실행 파일이므로 직접 실행할 수 있으며 내보낸 개체가 반환됩니다. 발신자에게.
코어 모듈은 자바스크립트 코어 모듈과 내장 모듈로 구분됩니다. JavaScript 핵심 모듈은 Node 프로세스가 시작될 때 메모리에 로드되며 process.bind process.binding()
process.binding()
메소드를 통해 실행될 수 있습니다. get_builtin_module()
및 register_func()
함수 처리.
또한 핵심 모듈, 즉 파일 모듈->JavaScript 코어 모듈->내장 모듈을 도입하기 위한 Node용 참조 체인도 찾았습니다. 모듈은 외부적으로 캡슐화를 구현합니다 .