- Today
- Total
빵 입니다.
CommonJS(CJS) V.S. ECMAScript Modules(ESM) 본문
📌 모듈 시스템
◾ 자바스크립트에서 모듈 시스템은 코드를 효율적으로 분리하고 재사용 가능하게 만드는 역할을 한다.
◾ CommonJS와 ESM은 모듈화를 지원하는 두 가지 방식이다.
📌 CommonJS
🧿 Node.js 환경에서 사용
- Node.js 초기 설계 단계에서 채택된 모듈 시스템
🧿 require() 함수로 모듈을 가져오고, module.exports로 내보낸다.
// math.js
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};
// app.js
const math = require('./math');
console.log(math.add(2, 3)); // 5
🧿 동기적 모듈을 로딩
- 동기적으로 모듈을 로드하기 때문에 모듈이 로드될 때까지 코드 실행이 중단된다.
- 서버 사이드 렌더링과 같은 환경에서 적합하다.
=> 서버 사이드에서는 모든 모듈이 로드된 후에야 코드가 실행되기 때문이다.
- 브라우저 환경에서 사용하기 어렵다.
=> 브라우저에서는 비동기 로드가 필수적이기 때문이다.
🧿 트리 셰이킹(Tree Shaking)이 어렵다.
* 트리 셰이킹은 사용되지 않는 코드를 제거하여 번들 크기를 줄이는 기술
require 함수는 런타임에서 동적으로 호출된다.
즉, 코드 실행 중에 어떤 모듈이 필요한지 결정되는데 빌드 도구(Webpack, Rollup 등)는 정적 분석을 기반으로 작동하기 때문에, 빌드 시점에 코드에서 실제로 어떤 모듈이 사용될지 확실히 알 수 없다.
그래서 빌드 도구는 모든 모듈을 잠재적으로 로드해야 하므로 사용되지 않는 코드까지 모두 번들에 포함시키게 된다.
* 런타임에서 동적으로 호출된다는 것 : 코드를 실행하면서 그때그때 필요한 모듈을 가져오는 방식을 의미한다.
* 정적 분석(Static Analysis) : 코드 실행 없이 소스 코드를 분석하는 방법
🧿 require 동작 방식
CommonJS에서 require는 동적으로 모듈을 가져오는 함수이다.
require로 모듈을 가져오면, 모듈의 코드가 실행되고, 해당 모듈이 반환하는 객체가 반환된다.
이 과정에서 모듈을 한 번만 로드하고 이후에는 캐시된 값을 반환한다.
즉, 같은 모듈을 여러 번 require하더라도 처음 로드한 결과를 그대로 사용한다.
CommonJS 모듈 시스템에서는 모듈의 코드가 실행되면서 그 모듈의 모든 부분이 메모리에 로드된다.
모듈 내에서 어떤 함수나 변수를 사용하든, 모듈 전체가 메모리에 적재되기 때문에, 사용하지 않는 부분도 메모리 상에 존재한다.
실제로 사용하는 부분만을 로드하는 ESM과 달리, CommonJS는 사용하지 않는 코드도 모두 메모리에 적재하게 된다.
따라서 실제 사용 여부와 상관없이 모든 코드가 번들에 포함될 가능성이 높다.
📌 ES Module
◾ 자바스크립트 언어에서 공식적으로 정의된 모듈 시스템으로, 2015년 ECMAScript 6(ES6)에서 도입되었다.
🧿 모듈을 가져올 때 import 문을, 내보낼 때 export 문을 사용한다.
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// app.js
import { add } from './math.js';
console.log(add(2, 3)); // 5
🧿 비동기적 모듈 로딩
- 비동기적으로 모듈을 로드하기 때문에 브라우저와 같은 환경에서 적합하다.
- 브라우저에서 <script type="module">로 직접 사용할 수 있는데, 이 과정은 비동기적으로 이루어딘다.
즉, 브라우저는 ESM을 로드하고 실행하는 동안 HTML 페이지의 다른 부분을 계속 처리할 수 있다.
🧿 비동기 로드가 중요한 이유
- 페이지 로딩 속도 개선
=> ESM은 비동기 로드 방식 덕분에 HTML 문서를 블로킹하지 않는다. 따라서 브라우저는 모듈을 로드하면서 동시에 페이지의 다른 리소스를 처리할 수 있어 페이지 로딩 속도가 개선된다.
=> 과거 <script> 태그를 사용할 때는 기본적으로 동기 방식으로 동작했기 때문에, 스크립트를 로드하는 동안 HTML 렌더링이 중단될 수 있었다.
// 과거 동기 방식
<script src="main.js"></script>
// 브라우저는 main.js를 로드하고 실행하기 전까지 나머지 HTML을 처리하지 않는다.
// ESM 비동기 방식
<script type="module" src="main.js"></script>
// main.js는 비동기로 로드되며, 나머지 HTML은 동시에 처리된다.
- 모듈 의존성 관리
=> ESM은 모듈 간 의존성을 자동으로 처리한다.
=> 모듈이 다른 모듈을 import할 경우, 브라우저는 필요한 모듈들을 비동기로 로드하면서 최적의 순서로 실행한다.
// main.js
import { helper } from './helper.js';
helper();
// 브라우저는 helper.js를 먼저 로드하고, 이후 main.js를 실행한다.
// 모든 과정은 비동기로 처리되므로 페이지 로딩 속도에 최소한의 영향을 미친다.
- 네트워크 효율성
=> ESM은 HTTP/2의 멀티플렉싱(Multiplexing)을 활용하여 모듈을 병렬로 로드할 수 있다.
=> 네트워크 리소스를 더 효율적으로 사용하며, 동기 로드 방식보다 빠르다.
- Lazy Loading
=> ESM은 동적으로 필요한 모듈만 로드할 수 있는 Dynamic Import를 지원한다.
=> 이 방식은 초기 로딩 시간을 줄이고, 특정 기능이 필요할 때만 로드함으로써 전체 성능을 향상시킨다.
if (condition) {
import('./feature.js').then((module) => {
module.runFeature();
});
}
🧿 비동기 로드와 동작 방식
1. HTML 파싱 중 비동기 로드 시작
<script type="module"> 태그를 만나면 브라우저는 지정된 모듈 파일을 백그라운드에서 비동기적으로 가져온다.
2. 의존성 로드
ESM은 정적 분석을 통해 필요한 의존성을 미리 파악하여 병렬로 로드한다.
3. 모듈 실행
모든 의존성이 로드되면 모듈의 실행이 시작됩니다. 실행은 HTML 파싱이 완료된 후 발생할 수 있다.
🧿 트리 셰이킹(Tree Shaking)이 용이하다.
- ESM은 정적 분석을 지원하므로, 어떤 모듈이 필요한지 미리 알 수 있어 불필요한 모듈을 제외하고 번들링하기 때문에 번들 크기를 줄일 수 있고, 번들을 최적화를 할 수 있다.
- 정적으로 구조화된 import와 export 문법을 사용하기 때문에 빌드 도구는 모듈 간의 의존성을 빌드 시점에 분석할 수 있어 사용되지 않는 코드를 안전하게 제거할 수 있다.
🧿 부분 내보내기와 기본 내보내기(export와 export default)
- 개별적으로 필요한 부분만 부분 내보내기 할 수 있고, 코드 전체를 기본 내보내기 할 수 있다.
◾ 부분 내보내기 (Named Exports)
- 여러 개의 값을 내보낼 때 사용된다.
- 각 값은 고유한 이름을 가지며, 이를 가져올 때는 동일한 이름으로 가져와야 한다.
📍 특징
- 다중 내보내기 가능
=> 하나의 모듈에서 여러 값을 내보낼 수 있다.
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
- 필요한 값만 선택적으로 가져오기 가능
=> 가져올 때 특정 값만 가져올 수 있다.
// app.js
import { add, subtract } from './math.js';
console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3
- 이름 변경 가능 (Alias)
=> as 키워드를 사용해 이름을 변경할 수 있다.
// app.js
import { multiply as mul } from './math.js';
console.log(mul(2, 3)); // 6
◾ 기본 내보내기 (Default Export)
- 모듈당 하나의 값만 내보낼 때 사용된다.
- 기본적으로 이름이 없는 익명 내보내기를 의미하며, 가져올 때는 가져오는 쪽에서 이름을 자유롭게 지정할 수 있다.
// greet.js
export default function greet() {
console.log('Hello, world!');
}
// app.js
import greet from './greet.js';
greet(); // Hello, world!
📍 특징
- 모듈당 한 개의 기본 내보내기
=> 기본 내보내기는 한 모듈에서 한 번만 사용할 수 있다.
export default function sayHello() { ... } // 가능
export default function sayGoodbye() { ... } // 하나 더 추가 불가능
- 가져올 때 이름 지정 가능
=> 기본 내보내기는 가져올 때 이름을 자유롭게 설정할 수 있다.
import hello from './greet.js';
hello(); // Hello, world!
- 동시에 다른 값과 함께 내보내기 가능
=> 본 내보내기와 부분 내보내기를 함께 사용할 수 있다.
export const name = 'John';
export default function greet() {
console.log('Hi!');
}
◾ 혼합 사용
- 기본 내보내기와 부분 내보내기를 동시에 사용할 수도 있다.
// user.js
export const name = 'Alice';
export const age = 30;
export default function greet() {
console.log(`Hello, ${name}!`);
}
// app.js
import greet, { name, age } from './user.js';
greet(); // Hello, Alice!
console.log(name); // Alice
console.log(age); // 30
👉🏻 요약
// 부분 내보내기 (Named Exports)
import { add, subtract } from './math.js';
// 기본 내보내기 (Default Export)
import greet from './greet.js';
// 혼합 가져오기
import greet, { name, age } from './user.js';
// 모든 값을 한 번에 가져오기
// * as를 사용해 모듈 전체를 가져올 수 있다.
import * as MathUtils from './math.js';
console.log(MathUtils.add(2, 3));
console.log(MathUtils.subtract(5, 1));
- 부분 내보내기는 여러 유틸리티 함수, 상수, 또는 다양한 기능을 제공하는 모듈에 적합하다.
- 기본 내보내기는 특정 모듈의 주요 기능을 내보낼 때 적합하다.
- 복잡한 모듈에서는 혼합 방식을 활용하여 주요 기능은 기본 내보내기로, 부가적인 기능은 부분 내보내기로 관리할 수 있다.
📌 CommonJS와 ES Module의 통합
- CommonJS와 ES Module을 모두 지원하는 라이브러리가 많이 등장한다.
- 이 두 모듈 시스템의 장점을 활용할 수 있다.
- Webpack 같은 번들러 사용하면 CommonJS와 ES Module를 모두 지원한다. => UMD(Universal Module Definition) 형식
📌 요약
특징 | CommonJS(CJS) | ECMAScript Modules(ESM) |
도입 시기 | Node.js 초기 (2009년경) | ES6(ES2015) |
내보내기, 가져오기 키워드 | require, module.exports | import, export |
사용 환경 | Node.js(서버 환경) | 브라우저, Node.js(최신 버전) |
로딩 방식 | 동기적 로딩 | 비동기적 로딩 |
트리 쉐이킹 지원 | ❌ | ⭕ |
코드 분석 | 런타임 분석 | 정적 분석 |
챗GPT 선생님과 함께 공부해 보았습니다.
'프론트엔드 > javascript' 카테고리의 다른 글
화살표 함수 중괄호 {} (0) | 2025.01.17 |
---|---|
performance.now() (0) | 2024.02.29 |
비트 NOT 연산자 (~ 연산자) (0) | 2023.05.10 |
자바스크립트 옵셔널 체이닝 (Optional chanining) (0) | 2022.07.22 |
자바스크립트 런타임과 엔진 그리고 동작 원리 (0) | 2022.07.18 |