Node.js모듈과 패키지

ESM과 CommonJS interop

Node.js에서 ESM과 CommonJS를 섞어 쓸 때의 기본 규칙, `import()`, `createRequire()` 활용 기준을 정리합니다.

마지막 수정 2026년 3월 22일

기본 패턴

javascript
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);
const pkg = require("./legacy.cjs");

설명

  • Node.js는 ESM과 CommonJS를 모두 지원하지만, 둘은 단순히 문법만 다른 것이 아니라 로딩 규칙과 런타임 메타데이터도 다릅니다. 그래서 섞어 쓰기 시작하면 interop 규칙을 정확히 알아야 합니다.
  • ESM 안에서는 기본적으로 require, __filename, __dirname이 없습니다. 이럴 때 CommonJS 모듈을 꼭 불러와야 하면 createRequire()가 안전한 다리 역할을 합니다.
  • CommonJS 쪽에서는 정적 import를 바로 쓸 수 없지만, import()는 비동기적으로 ESM을 불러오는 데 사용할 수 있습니다. 즉 방향에 따라 다리가 다릅니다.
  • CommonJS 모듈을 ESM에서 가져올 때는 대체로 module.exports가 default export처럼 보이게 되고, named export처럼 쓰는 경우는 기대와 다를 수 있습니다. 이 지점이 실무에서 가장 자주 헷갈립니다.
  • 좋은 전략은 가능하면 프로젝트 전체 모듈 방식을 일관되게 유지하고, interop는 필요한 경계에서만 최소화하는 것입니다. 섞어 쓸 수 있다는 사실과 섞어 써야 한다는 사실은 다릅니다.

빠른 정리

상황자주 쓰는 방법
ESM 안에서 CommonJS 사용createRequire(import.meta.url)
CommonJS 안에서 ESM 사용import()
프로젝트 전반 모듈 통일가장 안전한 선택
ESM에서 CJS 가져오기default 성격으로 읽히는 경우가 많음
핵심 원칙interop는 경계에서만 최소화

주의할 점

ESM과 CommonJS를 자유롭게 섞을 수 있다고 해서 아무 파일에서나 왕복하는 구조를 만들면 실행 방식과 테스트 환경이 금방 복잡해집니다. 경계 파일을 명확히 두는 편이 훨씬 낫습니다.

참고 링크

2 sources