꾸르빅 블로그
[디자인 패턴 바이블] 2장. 모듈 시스템 본문
들어가기에 앞서 Key Point
- 모듈이 왜 필수적이며 Node.js에서 다른 모듈 시스템이 가능한 이유
- Node.js는 두가지 모듈 시스템(CommonJs , ECMAScript module)을 사용하는데 이 두가지 형태가 왜 존재하는지
2-1. 모듈과 모듈 시스템
- 모듈 시스템(Module System) : 모듈을 정의하고 사용할 수 있게 해주는 도구이자 문법
- 모듈(Module) : 소프트웨어의 실제 유닛
- 좋은 모듈 시스템은 아래와 같은 특징이 있다.
1) 코드베이스 분할 방법 제시 : 구조적으로 관리할 수 있으며 독립적인 기능의 조각으로 개발할 수 있도록 가이드
2) 코드의 재사용 : 기능을 간단하고 구조화하여 다른 프로젝트에서 사용가능하도록 개발
3) 은닉성 : 복잡한 구현을 숨기고 간단한 인터페이스를 노출
4) 종속성 관리 : 모듈 실행 시 필요한 종속성들을 쉽게 import할 수 있도록 관리
2-2. Javascript, Node.js에서의 모듈 시스템
- Javascript는 초반에 내장 모듈시스템 없이 코드베이스 분할(1번항목), <script>태그를 통한 쉬운 import(4번항목)가 가능했으나, Javascript 브라우저 애플리케이션이 점점 복잡해지고 여러 프레임워크가 사용되면서 Javascript 프로젝트에 효율적으로 사용될 모듈 시스템 정의가 필요했다. 이에 성공적 결과를 낸 것이 AMD와 UMD이다.
※ AMD(Asynchronous Module Definition) : 비동기 모듈에 대한 표준안. 브라우저는 모든 모듈이 다 로딩될 때까지 기다릴 수 없기 때문에 비동기 모듈 로딩 방식으로 구현되어있다. RequireJS에 의해 대중화되었다.
※ UMD(Universal Module Definition) : CommonJS와 AMD를 통합하기 위한 표준안.
- Node.js는 운영체제의 파일 시스템에 직접적으로 접근할 수 있기 때문에, 브라우저가 아닌 환경에서 Javascript 모듈을 제공할 수 있도록 고안된 CommonJS가 탄생하였다. 이후 Browserify와 Webpack과 같은 모듈 번들러 덕분에 브라우저 환경에서도 사용하게 되었다.
- 이후 2015년도에 ES6의 발표와 함께 표준 모듈 시스템(ESM)의 제안이 나왔다. 이에 따른 구체적인 구현을 제공하지 않았기 때문에 Node.js 13.2 버전부터 ESM에 대한 안정적인 지원을 한다. 따라서 현재는 CommonJS가 주된 모듈 시스템이라고 볼 수 있다.
2-3. 모듈 시스템과 패턴
2-3-1. 노출식 모듈 패턴
- 은닉의 필요성 : Javascript는 네임스페이스가 없어 모든 스크립트가 전역 범위에서 실행된다. 따라서 애플리케이션 코드나 종속성 라이브러리가 노출된다면 스코프가 오염되어 코드 충돌이 날 수 있다. 또한 애플리케이션이 확장됨에 따라 개별적이고 분리된 기능 구현이 필요하다.
- 노출식 모듈 패턴(revealing module pattern) : 자기 호출 함수(즉시 실행 함수 표현-IIFE)를 사용하여 private 범위를 만들고 공개될 부분만 내보내는 방식.
const myModule = (() => {
const privateFoo = () => {}
const privateBar = []
const exported = {
publicFoo: () => {},
publicBar: () => {}
}
return exported
})() //괄호 파싱 시 함수 호출
console.log(myModule) // publicFoo와 publicBar로 구성된 객체 반환
console.log(myModule.privateFoo, myModule.privateBar) // undefined 반환
2-4. CommonJS 모듈
- CommonJS 명세의 두가지 주요 개념 요약
1) require는 로컬 파일 시스템으로부터 모듈을 import하게 해준다.
2) exports와 module.exports는 특별한 변수로서 현재 모듈에서 공개될 기능들을 내보내기 위해서 사용된다.
2-4-1. 직접 만드는 모듈 로더
- 노출식 모듈 패턴을 이용한 Node.js require() 함수 모방
function loadModule(filename, module, require) {
const wrappedSrc =
'(function (module, exports, require) {
${fs.readFileSync(filename, 'UTF8')}
})(module, module.exports, require)' //module.exports 사용 주의
eval(wrappedSrc)
}
function require(moduleName) {
const id = require.resolve(moduleName) //모듈 전체 경로 로드
if(require.cache[id]) { //캐싱되어 있는 경우 캐시값을 가져옴
return require.cache[id].exports
}
//모듈 메타 데이터: 최초 로드를 위해 exports 속성 초기화된 module 객체 생성
const module = {
exports: {}, //로드한 모듈 코드에서의 public API export용
id
}
require.cache[id] = module //캐시 업데이트
loadModule(id, module, require) //모듈소스코드를 읽어와 module.exports 객체에 public API를 넣음
return module.exports
}
require.cache = {}
require.resolve = (moduleName) => {
/* 모듈 이름으로 모듈의 전체 경로를 찾아냄 */
}
2-4-2. 모듈 정의
- module.export 변수에 할당되지 않다면 비공개
const dependency = require('./anotherModule')
//private함수
function log() {
console.log('Well done ${dependency.username}')
}
//공개적으로 사용되기 위해 익스포트되는 API
module.exports.run = () => {
log()
}
2-4-3. module.exports VS exports
- module.exports는 require에서 사용되는 return 값이며, exports는 module.exports의 참조값이다.
따라서, export의 재할당은 module.exports에 영향을 미치지 않는다. exports 사용 시 새로운 속성(property)을 추가할 수 있다
const module = { exports: {} }
const exports = module.exports
exports.hello = () => {
console.log('Hello')
}
module.exports = () => {
consloe.log('Hello')
}
참고자료
- AMD와 UMD
https://velog.io/@rlaclgns321/%EB%AA%A8%EB%93%88-%EC%8B%9C%EC%8A%A4%ED%85%9CCommonJS-AMD-UMD-ES6
- module.exports 대 export
'도서 > Node.js' 카테고리의 다른 글
| [디자인 패턴 바이블] 4장. 콜백을 사용한 비동기 제어 흐름 패턴 (0) | 2024.03.24 |
|---|---|
| [디자인 패턴 바이블] 3장. 콜백과 이벤트 (2) (0) | 2024.03.24 |
| [디자인 패턴 바이블] 3장. 콜백과 이벤트 (1) (0) | 2023.06.04 |
| [디자인 패턴 바이블] 1장. Node.js 플랫폼 (0) | 2023.03.05 |
| [디자인 패턴 바이블] 0. Design Pattern 공부 시작 (0) | 2022.10.09 |