모듈(Module)
여태까지 우리는 간단하게 10줄이 넘지 않는 코드들만 봐왔다. 모두 한 파일에 작성한다고 당연히 생각이 들겠지만, 규모가 작은 프로젝트는 괜찮으나 크기가 커지면 유지보수와 재사용을 위해 기능별로 파일 분리하는 것이 효율적이다.
이 때 이 분리된 파일들을 모듈이라고 부른다. ES6부터 네이티브 모듈 시스템이 추가되었다. 이전에는 CJS와 AMD와 같은 모듈 시스템이 사용되었다.
모듈의 특징
1. 파일 스코프를 가진다.
각 모듈은 자체적인 파일 스코프를 가진다. 전역 네임스페이스를 오염시키지 않기 때문에 변수나 함수의 충돌을 방지하고 코드를 분리된 단위로 유지할 수 있도록 한다.
2. 선택적 공개가 가능하다. (캡슐화) - export
자체적인 스코프를 가지기 때문에 내부 구현의 세부 사항을 숨길수가 있다. 예를 들어, test.js에 함수 A, B를 선언했다고 했을 때 해당 파일을 모듈로 사용하더라도 A함수만 모듈로 사용하고 싶다면 A함수만 export 하면 된다. B함수에 접근할 수 없다.
3. 공개된 자원 중 일부 혹은 전체를 자신의 스코프 내로 불러들여 재사용이 가능하다. - import
예를 들어, 의자 만드는 공장을 모듈이라고 한다면 어떤 재료가 들어와도 재료만 주면 공장이 의자를 만들어준다. 재사용성이 뛰어난 것이다.
모듈 사용 방식은 크게 CJS, ESM 방식으로 나뉜다.
CJS(CommonJS)
CJS는 Node.js에서 사용되는 모듈 시스템이다. 표준 모듈 시스템은 아니다. Node.js 초기 버전부터 사용되었고, 현재까지도 널리 사용되고 있다. require()과 module.exports 키워드를 사용한다.
// math.js
function add(a,b){
return a + b;
}
function substract(a,b){
return a - b;
}
module.exports = {add, substract};
//main.js
const { add, subtract } = require("./math");
console.log(add(5,3));
console.log(substract(5,3));
math.js에서 add와 substract 2개의 함수를 정의했고 이를 `module.exports`를 통해 한꺼번에 export 했다.
main.js에서 이를 `require`로 import했고, add와 substract 함수 2개를 사용했다.
CJS는 동적 분석이다
CJS는 모듈간의 종속성을 모두 파악하기 전에 파일이 읽힘과 동시에 실행된다.
if( a === true){
const { aModule } = require('a.js');
}
위와 같이 원하는 시점에 모듈을 import 할 수 있다는 것도 동적인 특성 덕분이다. 어디서든 불러와도 상관없다는 장점이 있으나 정적 분석(종속성 파악 등)을 하지 않기 때문에 순환 참조가 안 되는 문제가 생긴다.
순환 참조 불가 - CJS에는 시간차가 있다
다음과 같은 예시가 있다.
a = { b:{} }
b = { a: {b}}
a함수는 b를 부르고, b 함수는 a 함수를 부르고 있다 CJS라면 어떻게 동작할까? 결국 문제가 발생한다.
앞서 CJS가 동적 분석이라고 설명했다. 먼저 모듈간의 종속성은 파악하지 않는다. 그러면 a는 b를 가지고 있지만, b는 a를 가지고 있지 않는 것이 된다. ESM에서 더 설명하겠다.
SSR(Server Side Rendering)혹은 런타임에서 사용된다
SSR은 서버 측에서 웹 페이지를 렌더링하여 클라이언트에게 보내는 방식을 말한다. 클라이언트는 초기 페이지 로드시 서버에서 생성된 HTML 받아 화면에 표시하고, 그 이후에는 클라이언트 측 JS를 사용해 상호작용한다.
(클라이언트에서 HTML 같은 정적 콘텐츠를 생성하는 대신, 서버에서 웹 페이지를 동적으로 렌더링하여 전달)
즉 백엔드에서 모든 내용을 빌드해서 전달하기 때문에 검색 엔진이 콘텐츠를 쉽게 인덱싱 할 수 있다. 또한, 모든 컨텐츠의 표시가 가능하다. 당연히 프레임워크나 라이브러리에 의존하지 않고도 사용 가능하다. 단, 서버 부하를 늘릴 수 있고 구현이 복잡할 수 있다.
ESM(ES Modules, ECMAScript)
ESM은 ECMAScript 표준의 일부다. 기존의 CJS와 다르게 정적인 `import`와 `export` 문을 사용하여 모듈을 정의하고 로드한다. 모듈을 정의할 때 `export`문을 사용하여 외부로 공개할 수 있는 기능을 정의하고, 다른 파일에서 `import` 문을 사용해 해당 기능을 가져온다.
비동기 import가 불가능하며 정적 분석이다
if (condition) {
import module1 from 'module1';
} else {
import module2 from 'module2';
}
앞서 CJS가 동적 분석이라고 기술했다. 언제든 부르는 순간에 모듈을 import 할 수 있지만, 쉽게 설명하자면 미리 읽어보지 않고 내용을 가져오는 것이랑 똑같다.
ESM은 위 코드처럼 작성하는게 불가능하다. ESM은 정적 import를 통해 사용하지 않는 코드를 제거하는 등 코드 최적화를 해줄 수 있는 종속성 정적 분석을 가능하게 해주기 때문이다.
정적 분석
정적 분석은 소스 코드를 컴파일하거나 실행하기 전에 코드의 구조, 문법, 의미론적 오류, 품질 등을 분석한다.
또한, 모듈간 의존성을 미리 파악하고 모듈을 로드하는 시점이 런타임 이전에 결정된다. 그렇기 때문에 불필요한 모듈을 불러오지 않을 수 있어 우리가 아는 최적화의 개념과 일맥상 통한다.
정적 분석의 단계에는 몇가지가 있는데 이 포스팅에서 그렇게까지 깊이 있는 내용까진 다루지 않는다. 다만, ESM은 정적 분석의 단계를 거치기 때문에 import를 비동기적으로 사용할 수 없다는 것은 알아야한다. 일단 require()를 만나자마자 import하는 CJS와 달리 안정성이 높다는 장점이 있다.
Default Export/import
모듈당 하나의 값을 내보낼 때 사용한다. 모듈 전체가 하나의 클래스거나 객체일 때 유용하며, import 시 명칭 변경이 가능하다. export default는 단일 책임 원칙을 권장하고, 깔끔한 하나의 인터페이스만을 제공하기에 적합하다. 사용자 관점에서 볼 때 바인딩을 위한 이름을 정확이 알 필요 없다는 것도 장점이다.
export default function greet() {
console.log(`Hello, ${name}!`);
}
---------------------------------------------------------------
import greet from './module.js'; //{greet}로 쓰지 않아도 좋다.
greet(); // 출력: "Hello, John!"
Named Export/Import
여러 값을 내보낼 때 각각의 고유한 이름으로 내보내기 할 때 사용한다. 하나의 모듈에서 여러 함수, 변수, 클래스 등을 내보낼 때 사용한다.
namedExport.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => a / b;
// index.js
import {add, subtract, miltiply, divide} from './namedExport.js'
그렇다면, 모듈 시스템은 어떤 것을 사용해야할까
당연히 프로젝트의 요구 사항과 개발 환경을 고려해서 사용해야 한다.
CommonJs(CJS)
주로 백엔드 개발에 사용된다. 초기 Node.js를 활용한 SSR 개발시 사용됐다. 기존 node.js 프로젝트를 사용하거나 라이브러리를 사용하는 경우에 사용할 수 있다.
ES Module(EMS)
최신 웹 개발 환경을 지원하기 때문에 적합하다. 또한, 비동기적인 동작을 제한한 대신에 전체 파일 구조를 파악할 수 있게 되어 최적화가 가능해졌고, 순환 종속성 문제를 해결할 수 있어 안정적이다. 하지만, ES6가 등장하기 전에 CJS 기반으로 만들어진 웹이 분명 존재하기 때문에 CJS를 이해하는 것도 중요한 사실 중에 하나일 것이다.
Reference
[1] https://velog.io/@kakasoo/ESM%EA%B3%BC-CommonJS%EC%9D%98-%EC%B0%A8%EC%9D%B4
ESM과 CommonJS의 차이
사실 CommonJS는 표준이 아니에요.조금 역사를 이야기할 필요가 있는데, 아시다시피 JavaScript는 프론트 언어였습니다. 그런데 프론트에서는 HTML 문서 상에 script 태그를 이용해서 JavaScript 코드를 전
velog.io
ESM과 CJS의 차이를 알기에 읽어보면 좋은 글이다.
'Programming > JavaScript' 카테고리의 다른 글
[JS] 자바스크립트 this, 바인딩, call()/apply()/bind() (0) | 2024.03.27 |
---|---|
[JS] 자바스크립트 객체 (0) | 2024.03.26 |
[JS] 자바스크립트 실행 컨텍스트, 스코프 체인, environmentRecord/outerEnvironmentReference (0) | 2024.03.26 |
[JS] 자바스크립트 조건문, 반복문 (0) | 2024.03.24 |
[JS] 자바스크립트 함수, 순수 함수/일급 함수,함수 표현식/함수 선언식/화살표 함수, 함수 호이스팅 (0) | 2024.03.24 |