2025년 04월 21일
Node.js에는 CommonJS(CJS)와 ECMAScript Modules(ESM)이라는 두 가지 모듈 시스템이 존재한다.
두 시스템은 모듈을 내보내고 가져오는 문법이 다르고, 서로 호환되지 않기 때문에 사용 시 각 시스템에 맞는 적절한 문법을 사용해야한다.
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
module.exports = {
add,
multiply,
};
const math = require('./math.cjs');
console.log(math.add(2, 3)); // 5
console.log(math.multiply(4, 5)); // 20
const lang = 'ko';
const locale = require(`./i18n/${lang}.js`);
if (process.env.NODE_ENV === 'development') {
const dev = require('./dev-tools.js');
dev.init();
}
(async () => {
const esmModule = await import('./math.mjs');
console.log(esmModule.add(2, 3));
})();
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
import { add, multiply } from './math.mjs';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
import/export 구문을 사용한다.
파일 확장자를 명시해야 한다.
default export를 통해 특정 모듈 하나만 기본값으로 내보낼 수 있다.
정적 방식(import 시점에 분석)이므로 조건부 로딩이 불가하다. -> 해결하려면 import() 사용
// ❌
if (process.env.NODE_ENV === 'development') {
import { foo } from './dev-tools.js'; // SyntaxError
}
구문 분석 단계에서 모듈을 불러오기 때문에 동적으로 값을 넣거나 바꾸기 어렵다. -> 해결하려면 import() 사용
// ❌
const lang = 'ko';
import(`./i18n/${lang}.js`); // SyntaxError
동적으로 모듈을 불러오기 위해서는 import()를 사용한다.
정적 분석 방식이기 때문에 Tree Shaking이 가능하다.
CommonJS 모듈을 불러올 수 있다.
import cjsModule from './math.cjs';
console.log(cjsModule.add(1, 2));
CommonJS (CJS) | ECMAScript Modules (ESM) | |
---|---|---|
파일 확장자 | .js, .cjs | .mjs, .js + "type": "module" |
확장자 생략 | 가능 | 불가능 |
내보내기 | module.exports | export, export default |
가져오기 | require() | import, import() |
동기/비동기 | 동기 | 비동기 (asynchronous) |
조건부 로딩 | 가능 | 불가능 |
동적 경로 지정 | 가능 | 불가능 |
트리 쉐이킹 | 어려움 | 가능 |
그렇다면, Node.js는 어떤 기준으로 파일의 모듈 시스템을 결정할까?
(https://nodejs.org/api/packages.html#determining-module-system 번역)
Node.js는 다음과 같은 경우 ES 모듈로 처리한다. 이는 node 명령어로 직접 실행했거나, import 문이나 import() 표현식으로 참조했을 때 해당된다:
Node.js는 다음과 같은 경우 CommonJS 모듈로 처리한다.:
→ 즉, Node.js는 이런 “모호한” 파일을 우선 CommonJS로 해석해서 실행해보고, 실패할 경우에만 ES 모듈로 다시 해석을 시도한다.
주의 사항
모호한 파일 안에 ES 모듈 문법을 섞어 쓰면 성능 저하가 발생할 수 있으므로, 가능한 한 명시적으로 작성하는 것이 권장된다. 특히 모든 소스 코드가 CommonJS로 되어 있더라도 package.json에 "type" 필드를 반드시 명시적으로 포함하는 것이 좋다. 향후 Node.js의 기본 모듈 타입이 바뀌더라도 패키지의 안정성을 보장할 수 있고, 빌드 도구나 로더들이 파일을 어떻게 해석해야 할지 더 쉽게 판단할 수 있기 때문이다.
Node.js는 모듈 유형이 명확하지 않은 모호한 입력의 경우, 소스 코드를 직접 분석하여 ES 모듈 문법이 포함되어 있는지 감지한다. 만약 ESM 문법이 감지되면 해당 입력은 ES 모듈로 처리된다.
모호한 입력이란:
다음 중 하나에 해당하는 경우를 말한다:
ES 모듈 문법
ES모듈 구문은 CommonJS로 평가될때 에러가 나는 구문으로 정의된다. 이는 다음을 포함한니다:
참고자료
https://nodejs.org/api/packages.html#determining-module-system
https://tech.kakao.com/posts/605
https://toss.tech/article/commonjs-esm-exports-field