Node.js 인터뷰 질문 38
질문: Node.js의 모듈 시스템에 대해 설명하고, CommonJS와 ES 모듈의 차이점을 설명해주세요.
답변:
Node.js의 모듈 시스템은 코드를 구조화하고 재사용하기 위한 핵심 메커니즘입니다. Node.js는 초기에 CommonJS 모듈 시스템을 채택했으며, 최근 버전에서는 ECMAScript(ES) 모듈 시스템도 지원하고 있습니다. 두 모듈 시스템은 각각 다른 문법과 동작 방식을 가지고 있습니다.
CommonJS 모듈 시스템
CommonJS는 Node.js가 처음부터 사용한 모듈 시스템으로, 서버 측 JavaScript 환경에서 모듈을 정의하고 사용하는 방법을 제공합니다.
주요 특징
- 동기적 로딩: CommonJS 모듈은 동기적으로 로드됩니다. 모듈을 가져오는
require()
호출이 완료될 때까지 다음 코드가 실행되지 않습니다. - 런타임 평가: 모듈 코드는
require()
호출 시점에 평가됩니다. - 캐싱: 한 번 로드된 모듈은 캐시되어 동일한 모듈을 여러 번 요청해도 다시 로드되지 않습니다.
- 값으로서의 exports: 내보낸 값은 객체, 함수, 또는 다른 JavaScript 값이 될 수 있습니다.
기본 사용법
// 모듈 내보내기 (math.js)
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
// 방법 1: exports 객체에 속성 추가
exports.add = add;
exports.subtract = subtract;
// 방법 2: module.exports 사용 (객체 전체 대체)
module.exports = {
add,
subtract,
multiply: (a, b) => a * b,
};
// 또는 함수 직접 내보내기
module.exports = function (a, b) {
return a + b;
};
// 모듈 가져오기 (app.js)
const math = require("./math");
console.log(math.add(5, 3)); // 8
// 구조 분해 할당 사용
const { add, subtract } = require("./math");
console.log(add(5, 3)); // 8
ES 모듈 시스템
ES 모듈은 ECMAScript 표준의 일부로, 브라우저와 Node.js에서 모두 사용할 수 있는 표준화된 모듈 시스템입니다. Node.js는 버전 12부터 ES 모듈을 정식으로 지원하기 시작했습니다.
주요 특징
- 정적 구조: 모듈의 가져오기와 내보내기가 정적으로 선언되어, 분석 및 최적화가 가능합니다.
- 비동기 로딩: 모듈은 비동기적으로 로드될 수 있으며, 더 나은 성능을 제공할 수 있습니다.
- 모듈 단위 스코프: 각 모듈은 자체 스코프를 가지며, 모듈 내의 변수는 기본적으로 모듈 내부에서만 접근 가능합니다.
- 단일 엔트리포인트: 파일이 여러 번 임포트되더라도 모듈은 한 번만 평가됩니다.
- 언어 수준 지원: JavaScript 언어 자체의 기능으로, 별도의 라이브러리 없이 사용 가능합니다.
기본 사용법
// 모듈 내보내기 (math.mjs 또는 math.js with package.json: {"type": "module"})
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// 또는 기본 내보내기 사용
export default function multiply(a, b) {
return a * b;
}
// 모듈 가져오기 (app.mjs)
// 명명된 내보내기 가져오기
import { add, subtract } from "./math.mjs";
console.log(add(5, 3)); // 8
// 기본 내보내기 가져오기
import multiply from "./math.mjs";
console.log(multiply(5, 3)); // 15
// 모든 내보내기 가져오기
import * as math from "./math.mjs";
console.log(math.add(5, 3)); // 8
Node.js에서 ES 모듈 사용하기
Node.js에서 ES 모듈을 사용하는 방법은 여러 가지가 있습니다:
- 파일 확장자 사용:
.mjs
확장자를 사용하면 Node.js는 해당 파일을 ES 모듈로 처리합니다. - package.json 설정: 패키지의
package.json
파일에"type": "module"
을 추가하면.js
파일도 ES 모듈로 처리됩니다. - 명시적 확장자: ES 모듈에서는
import
문에 파일 확장자를 명시적으로 포함해야 합니다.
// package.json
{
"name": "my-app",
"version": "1.0.0",
"type": "module"
}
CommonJS와 ES 모듈의 주요 차이점
1. 문법 차이
// CommonJS
const module = require("./module");
exports.something = something;
module.exports = { something };
// ES 모듈
import module from "./module.js";
import { something } from "./module.js";
export const something = something;
export default something;
2. 로딩 메커니즘
CommonJS:
- 동기적으로 로드됩니다.
require()
가 호출될 때 모듈이 로드되고 평가됩니다.- 모듈 코드는 런타임에 평가됩니다.
ES 모듈:
- 정적 분석을 위해 2단계로 처리됩니다: 구문 분석 및 모듈 연결(링킹) 단계와 평가 단계.
import
선언은 파일의 맨 위에서 호이스팅됩니다.- 모듈 내보내기는 모듈 평가 후에만 액세스할 수 있습니다.
3. 동적 가져오기
CommonJS: 변수를 사용한 동적 가져오기가 가능합니다.
const moduleName = "some-module"; const module = require(moduleName);
ES 모듈: 정적
import
선언은 동적 경로를 지원하지 않지만, 동적 가져오기를 위한import()
함수를 제공합니다.const moduleName = "./some-module.js"; import(moduleName).then((module) => { // 모듈 사용 });
4. 순환 의존성 처리
- CommonJS: 부분적으로 완료된 객체의 캐시를 반환할 수 있어 순환 의존성 문제가 발생할 수 있습니다.
- ES 모듈: 모듈 연결 단계에서 순환 의존성을 처리합니다. 모든 모듈이 연결된 후 평가됩니다.
5. 모듈 객체 및 메타데이터
CommonJS:
module
,exports
,require
,__dirname
,__filename
객체가 제공됩니다.- 실행 중인 파일의 경로를 쉽게 얻을 수 있습니다.
ES 모듈:
- CommonJS 특정 변수를 제공하지 않습니다.
- 대신
import.meta
객체를 통해 모듈에 대한 메타데이터에 접근할 수 있습니다. - 파일 경로를 얻으려면
import.meta.url
을 사용할 수 있습니다.
// CommonJS
console.log(__dirname);
console.log(__filename);
// ES 모듈 (필요한 경우 경로 기능 재구현)
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__dirname);
console.log(__filename);
두 모듈 시스템 간의 상호 운용성
Node.js는 CommonJS와 ES 모듈 간의 상호 운용성을 제공하지만, 몇 가지 제약사항이 있습니다:
ES 모듈에서 CommonJS 모듈 사용:
- ES 모듈은 CommonJS 모듈을 가져올 수 있습니다.
- CommonJS의
module.exports
는 ES 모듈의 기본 내보내기(default export
)로 가져와집니다.
CommonJS 모듈에서 ES 모듈 사용:
- CommonJS는 동기적으로 작동하므로, ES 모듈을 직접
require()
할 수 없습니다. - 대신 동적
import()
함수를 사용하여 Promise를 통해 ES 모듈을 비동기적으로 로드할 수 있습니다.
- CommonJS는 동기적으로 작동하므로, ES 모듈을 직접
// CommonJS에서 ES 모듈 사용
(async () => {
const esModule = await import("./es-module.mjs");
esModule.default(); // 기본 내보내기 사용
esModule.namedExport(); // 명명된 내보내기 사용
})();
어떤 모듈 시스템을 사용해야 할까?
선택은 프로젝트의 요구 사항과 환경에 따라 달라집니다:
CommonJS 사용이 더 적합한 경우
- 기존 Node.js 코드베이스와의 호환성이 필요한 경우
- 동적 모듈 로딩을 광범위하게 사용하는 경우
- 레거시 Node.js 패키지에 의존하는 경우
ES 모듈 사용이 더 적합한 경우
- 브라우저와 서버 양쪽 모두에서 동작하는 코드를 작성하는 경우
- 최신 JavaScript 기능과 생태계를 활용하는 경우
- 트리 쉐이킹 및 정적 분석 도구를 활용하려는 경우
- 미래 지향적인 코드를 작성하려는 경우
모듈 시스템의 모범 사례
- 일관성 유지: 한 프로젝트 내에서는 가능한 한 하나의 모듈 시스템을 사용하세요.
- 명시적 확장자: ES 모듈을 사용할 때는 항상 파일 확장자를 포함하세요.
- package.json 설정: 프로젝트의 모듈 타입(
"type": "module"
또는"type": "commonjs"
)을 명시적으로 설정하세요. - 상대 경로 사용: 로컬 모듈을 가져올 때는 상대 경로를 사용하세요.
- 명시적 내보내기: 무엇을 내보내는지 명확히 하고, 너무 많은 항목을 내보내는 것을 피하세요.
요약
- CommonJS는 Node.js의 원래 모듈 시스템으로, 동기적 로딩과
require()/exports
문법을 사용합니다. - ES 모듈은 JavaScript 언어 표준의 일부로, 정적 구조와
import/export
문법을 제공합니다. - Node.js는 두 모듈 시스템을 모두 지원하며, 파일 확장자(
.mjs
,.cjs
) 또는package.json
설정으로 구분합니다. - 두 시스템 간에는 문법, 로딩 메커니즘, 동적 가져오기, 순환 의존성 처리 등의 차이점이 있습니다.
- 프로젝트의 요구 사항에 따라 적절한 모듈 시스템을 선택하고, 일관성을 유지하는 것이 중요합니다.
Node.js의 모듈 시스템을 이해하는 것은 효율적인 코드 구성과 재사용 가능한 코드를 작성하는 데 필수적입니다. ES 모듈은 JavaScript 생태계의 미래 방향을 나타내지만, CommonJS는 여전히 Node.js 애플리케이션에서 널리 사용되고 있습니다.