-
Node.JS 디자인 패턴 및 바이블 (2)JS 2021. 7. 24. 12:29728x90
2장에서는 모듈시스템에 대해서 다룹니다. Node뿐만 아니라 클라이언트 영역의 자바스크립트를 이해하는데도 많은 도움이 될 것입니다.
🚀 모듈의 필요성
코드베이스를 나누어 독립적인 기능의 조각들을 개발 및 테스트하는데 도움을 제공합니다.
코드의 재 사용성, 은닉성, 종속성을 관리합니다.
🚀모듈 시스템과 패턴
위 책에서는 직접 모듈 패턴을 작성하면서 자바스크립트의 모듈 패턴에 대한 이해를 높여줍니다.
👻노출식 모듈 패턴
우선 가장 간단한 방식 중 하나인 노출식 모듈패턴은 IIFE를 활용하여 외부에 은닉성을 가진 모듈을 생성하는 방식입니다.
👻CommonJS 모듈
노드의 기본적인 모듈 시스템으로 require를 통해 로컬 파일 시스템을 모듈로 import하고 export 및 module.export를 통해 외부로
노출할 데이터를 지정합니다.
아래의 코드는 책에서 제공하는 모듈시스템을 모방한 간단한 코드입니다. 전체 모듈시스템을 이해하는데 큰 도움이 될 것입니다.
const require = (moduleName) => { const id = require.resolve(moduleName); if (require.cache[id]) return require.cache[id].exports; const module = { exports: {}, id }; // exports는 module.exports의 초기값에 대한 참조 require.cache[id] = module; loadModule(id, module, require); return module.exports; }; require.cache = {}; require.resolve = (moduleName) => { // 모듈이름으로 id라 불리는 모듈의 전체경로를 찾아서 반환 // 동일한 이름의 모듈이 여러군데 존재하는 경우 우리는 종속성 문제가 발생합니다. // node의 모듈시스템은 로드되는 위치에 따라 다른버젼의 모듈을 로드하도록 하여 문제를 해결합니다. };
추가적으로 책에선 CommonJS의 순환종속성이란 문제에 대해서 언급하고 있습니다.
각각의 모듈이 서로를 참조하고 있는 경우, 로드되는 순서에 따라 불안정한 상태가 발생하게 됩니다.
규모가 있는 시스템에서는 필연적으로 발생할 수 밖에 없는 문제며 주의할 필요가 있습니다.
ESM에서는 이러한 문제를 어떻게 해결하는지에 대해선 나중에 알아보도록 하겠습니다.
이번에는 모듈을 통해 종속성을 로드할때의 다양한 매커니즘을 설명합니다.
우리가 많이 사용하는 라이브러리들의 구조를 이해할때 도움이 되는 내용들 입니다.
🍩export 지정하기 ( Named Exports )
public으로 오픈할 값들을 exports에 할당하는 방식입니다.
대부분의 node의 코어모듈에서 사용하는 패턴으로 CommonJS 명세에서 권장하는 방식입니다.
🍩함수 내보내기 ( SubStack 패턴 )
module.exports를 함수로 재할당하는 방식으로, 모듈에 대한 명확한 진입점을 제공합니다.
이는 모듈이 한가지 책임만 지는 원칙이라는 노드의 정신과도 부합합니다.
🍩클래스 내보내기
module.exports를 클래스로 재할당하는 방식으로, 사용자에게 새로운 인스턴스를 만들 수 있게 하면서 확장성을 열어줍니다.
여전히 모듈에 대한 단일 진입점을 제공하지만 서브스택 패턴과 비교했을 때, 더 많은 기능들 외부에 노출하게 됩니다.
🍩함수 내보내기
module.exports에 인스턴스를 재할당하는 방식입니다.
이러한 패턴은 싱글톤과 비슷합니다만, 싱글톤 처럼 전체 어플리케이션에서의 인스턴스의 고유성을 완벽하게 보장하진 못합니다.
이 방식의 문제는 클래스를 내보낸 것은 아니지만 사용자에 따라 얼마든지 새로운 인스턴스를 생성할 수 있다는 점입니다.
👻ESM
앞서 알아보았던 CommonJs의 문제인 순환종속성에 대한 지원과 비동기적 모듈 로드를 제공합니다.
또 다른 특징으로는 ES 모듈은 static으로 동적으로 생성할 수 없으며 상수문자열만 받을 수 있습니다.
노드에서는 기본적으로 CommonJS를 기반으로 동작합니다. 그래서 ESM을 사용하려면 아래의 조치가 필요합니다.
- 모듈 파일 확장자를 .mjs로 변경
- 모듈과 가장 근접한 package.json의 type 필드를 module로 명시
자바스크립트를 활용해보셨다면 export와 default export 구문을 보셨을 겁니다.
export는 이름을 가지기에 명확합니다. default export는 모듈에서 가장 핵심적인 기능 하나와 연결하는 편리한 방법입니다.
하지만 default export는 트리쉐이킹 작업을 어렵게 만드는 문제가 있어 위 책에서는 named export는 권장합니다.
저의 개인적인 생각도 동일합니다.
🍩 비동기 import
앞서 언급한바와 같이 ESM은 정적으로 동작합니다. 그래서 아래와 같은 문제점을 가집니다.
- 모듈 식별자는 실행 중에 생성 될 수 없다.
- 모듈의 import는 모든 파일의 최상위에 선언되며, 제어 구문내에 포함 될 수 없다.
이러한 문제를 해결하기 위해 ESM는 모듈 객체를 Promise로 반환하는 비동기 import를 지원합니다.
const SUPPORTED__LANGUAGE = ["..."]; const selected_language = process.argv[2]; if (!SUPPORTED__LANGUAGE.includes(selected_language)) process.exit(1); import(`./string-${selected_language}.js`).then((str) => { console.log(str); });
🍩 모듈 적재방식
순환 종속성을 ESM에서 어떻게 해결하는지 알기위해선 모듈의 적재방식에 대한 이해가 필요합니다.
ESM을 활용할때는 자바스크립트 인터프리터는 우선 종속성 그래프를 생성합니다.
entry point에서부터 DFS를 돌면서 모든 import 구문을 찾아서 메모리에 적재합니다.
적재한 개체들 간의 참조 종속성 관계를 파악합니다. 이후에 코드를 실행합니다.
CommonJS 와의 가장 큰 차이는 CommonJS는 non-static으로 종속성 그래프가 완성되기 전에 파일을 실행시킨다는 점입니다.
그래서 CommonJS는 if 구문이나 반복문에서도 require를 활용할 수 있게 됩니다.
CommonJS와의 또 다른 차이점은 CommonJS는 import되었을 때 개체 전체가 얕은 복사로 넘어와서 import한 모듈에서 값을
변화시킬 수 있다는 점입니다. 반대로 ESM은 읽기전용으로 넘어옵니다.
👻 CommonJS vs ESM
CommonJS는 파일의 확장자 선택적 / ESM은 파일 확장자 필수적
CommonJS는 strict mode 선택적 / ESM은 strict mode 필수적
CommonJS는 exports, module, __filename, __dirname 등을 통한 참조가능 / ESM은 불가능 (경로를 얻을때는 path 활용)
👻 CommonJS 와 ESM 상호운용
ESM 내부에서 export default의 경우에만 CommonJS 모듈을 임포트 가능합니다.
반대의 경우는 불가능합니다.
'JS' 카테고리의 다른 글
Node.JS 디자인 패턴 및 바이블 (4) (0) 2021.07.29 Node.JS 디자인 패턴 및 바이블 (3) (0) 2021.07.25 Node.JS 디자인 패턴 및 바이블 (1) (0) 2021.07.24 [Typescript] 1. 개론 (0) 2021.04.25 [Javascript] 6. 이벤트 처리 (0) 2021.04.25