// add.js
module.exports.add = (x, y) => x + y;
// main.js
const { add } = require('./add');
add(1, 2);
// add.js
export function add(x, y) {
return x + y;
}
// main.js
import { add } from './add.js';
add(1, 2);
require / module.exports 를 사용하고, ESM은 import / export 문을 사용한다1. Node.js 8.5 (2017년) - 가장 최초, 실험적 지원 시작
--experimental-modules 플래그 필요2. Node.js 10.x (2018) - 여전히 실험적, 기본 구조 정비
3. Node.js 12.17 (2020년 5월) - 플래그 없이 '실험적' ESM 도입
--experimental-modules없이 ESM 가능.mjs, "type": module 도입4. Node.js 13.2 (2019년 말) - ESM 지원 안정화 단계 진입
5. Node.js 14 LTS (2020년 10월) - 공식적인 안정화
6. Node.js 16 (2021) - ESM 사용자가 실무 다수로 전환
1. Node.js는 원래 CommonJS를 전제로 설계되었기 떄문
require() (동기)module.exports__dirname, __filenamerequire.cache2. ESM 스펙이 너무 늦게 확정됐다
3. npm 생태계가 이미 CommonJS에 "완전히 고착되어 있었기 때문
npm은 2010년 이후 수천만 패키지가 모두 CJS기반으로 만들어졌고
같은 Node 특유의 CJS 확장 규칙을 전제로 생태계가 굳어졌다.
이 상태에서 Node가 갑자기 ESM을 공식 채택하면
이런 대혼란이 발생함
즉 Node는 레거시 생태계를 깨지 않고 ESM을 넣어야 했기 떄문에 도입이 극도로 어렵고 느려질 수 밖에 없었다
4. CJS <-> ESM 상호운용성이 악몽 수준으로 어렵다
.js 파일이 CJS인지 ESM인지 판단해야 하는 규칙5. Node.js의 모듈 해석 규칙(require resolution)이 ESM과 충돌
Node의 CJS는 아래 같은 비표준 기능을 사용한다
require('loadsh') -> node_modules에서 자동 탐색require('./foo') -> 확장자 자동 추론 (.js/.json/.node)main 사용index.js 자동 로딩이런 규칙들은 ESM의 정적/URL 기반 모듈 해석 규칙과 전혀 맞지 않는다.
브라우저 ESM은
Node는 이 간극을 "최대한 안 깨지게" 메꾸느라 시간이 오래 걸렸음
6. ESM 도입을 늦춘 또 다른 이유 : 보안, 성능 문제
ESM은 본직적으로 비동기 로더 기반이라
이런 부분에서 Node core 팀은 매우 보수적이었음
Node의 철학은
"런타임 안정성과 backwards compatibility를 최우선으로 한다
그래서 조금이라도 위험한 변화는 수 년 검증 후에만 적용한다
7. 정리
Node는 CJS 기반으로 설계된 런타임이고, npm 생태계도 CJS를 기반으로 성장했기 때문에
완전히 다른 철학의 ESM을 통합하기 위해선 호환성, 성능, 스펙, 모듈 해석 규칙 모두를 재설계 해야 했으며
Node 팀은 호환성을 깨지 않으면서 ESM을 넣기 위해
모듈 로더를 두 개 유지하고 interop 계층을 만들 수 밖에 없었고,
이 작업이 난이도가 높아서 ESM 도입이 늦어졌다