Node는 두 가지 모듈 시스템을 지원합니다. CommonJS(CJS) — require/module.exports를 사용하는 최초의 방식 — 와 ES Modules(ESM) — import/export를 사용하는 표준 — 입니다. 이들은 문법, 로딩 동작, 상호 운용성(interop)에서 차이가 있습니다.
Node는 두 가지 모듈 시스템을 지원합니다. CommonJS(CJS) — require/module.exports를 사용하는 최초의 방식 — 와 ES Modules(ESM) — import/export를 사용하는 표준 — 입니다. 이들은 문법, 로딩 동작, 상호 운용성(interop)에서 차이가 있습니다.
// math.js
function add(a, b) { return a + b; }
module.exports = { add };
// app.js
const { add } = require("./math"); // synchronous, runtime
// math.mjs (or .js with "type": "module")
export function add(a, b) { return a + b; }
export default something;
// app.mjs
import { add } from "./math.mjs"; // static, hoisted
CommonJS ES Modules
Syntax require/module.exports import/export
Loading synchronous, runtime static, hoisted
Top-level await no yes
Tree-shaking hard yes (statically analyzable)
File extension .cjs / .js (default) .mjs / .js with "type":"module"
__dirname/__filename available not available (use import.meta.url)
// package.json
{ "type": "module" } // now .js files are treated as ESM
또는 .mjs 확장자를 명시적으로 사용합니다. 이를 지정하지 않으면 Node는 .js를 CommonJS로 기본 처리합니다.
// ESM can import CommonJS:
import pkg from "some-cjs-lib"; // works (default import)
// CommonJS CANNOT require() a pure-ESM package (it's async):
const esm = require("esm-only-pkg"); // ❌ ERR_REQUIRE_ESM
// must use dynamic import: const esm = await import("esm-only-pkg");
까다로운 부분: CJS는 ESM 전용 패키지를 require()할 수 없습니다. 이는 흔한 마이그레이션 고충입니다. 또한 ESM에는 __dirname이 없다는 점(대신 import.meta.url 사용)에 유의하세요.
두 시스템을 모두 아는 것은 실무 Node 작업에 필수적입니다. ESM은 미래이며(표준, tree-shaking 가능, top-level await, 브라우저와 정렬됨) 새 프로젝트에 적합한 선택이지만, 방대한 양의 npm 생태계와 기존 코드가 CommonJS입니다.
문법 차이, ESM 활성화 방법("type": "module"), 상호 운용성 제약(특히 CJS가 ESM을 require할 수 없다는 점)을 이해하면 이들을 섞을 때 자주 생기는 혼란과 오류를 방지할 수 있습니다.