icon안동민 개발노트

CommonJS와의 상호 운용성


 CommonJS와 ES 모듈 시스템은 JavaScript의 두 가지 주요 모듈 시스템입니다.

 이들의 상호 운용성을 이해하는 것은 현대 웹 개발에서 중요한 부분입니다.

CommonJS vs ES 모듈

 CommonJS 기본 문법

// 내보내기
module.exports = { foo: 'bar' };
// 또는
exports.foo = 'bar';
 
// 가져오기
const module = require('./module');

 ES 모듈 기본 문법

// 내보내기
export const foo = 'bar';
// 또는
export default { foo: 'bar' };
 
// 가져오기
import { foo } from './module';
// 또는
import module from './module';

 주요 차이점

  1. 문법 : CommonJS는 require/module.exports, ES 모듈은 import/export
  2. 로딩 : CommonJS는 동적, ES 모듈은 정적
  3. 실행 시점 : CommonJS는 런타임, ES 모듈은 파싱 시점

CommonJS 모듈 임포트

 타입스크립트에서 CommonJS 모듈을 임포트할 때 타입 추론 문제가 발생할 수 있습니다.

// 문제 발생 가능
const module = require('./commonjs-module');
 
// 해결 방안 1: 타입 어서션 사용
const module = require('./commonjs-module') as { foo: string };
 
// 해결 방안 2: import 문 사용 (esModuleInterop 옵션 필요)
import module from './commonjs-module';

ES 모듈을 CommonJS에서 사용하기

 tsconfig.json 설정

{
  "compilerOptions": {
    "module": "CommonJS",
    "esModuleInterop": true
  }
}

 이 설정은 ES 모듈 문법으로 작성된 코드를 CommonJS 환경에서 사용 가능한 형태로 컴파일합니다.

default와 named import/export 변환

 ES 모듈에서 CommonJS로

// ES 모듈
export default function foo() {}
export const bar = 'bar';
 
// CommonJS로 변환
exports.default = function foo() {}
exports.bar = 'bar';

 CommonJS에서 ES 모듈로

// CommonJS
module.exports = function foo() {}
module.exports.bar = 'bar';
 
// ES 모듈로 사용
import foo, { bar } from './module';

동적 임포트 in CommonJS

 CommonJS 환경에서도 동적 임포트를 사용할 수 있지만, 약간의 차이가 있습니다.

// ES 모듈
const module = await import('./module');
 
// CommonJS
const module = await import('./module').then(m => m.default || m);

esModuleInterop 옵션

 esModuleInterop 옵션은 CommonJS 모듈을 ES 모듈 문법으로 더 쉽게 임포트할 수 있게 해줍니다.

// esModuleInterop: false
import * as express from 'express';
const app = express.default();
 
// esModuleInterop: true
import express from 'express';
const app = express();

모듈 해석 전략

 tsconfig.json에서 모듈 해석 전략을 설정할 수 있습니다.

{
  "compilerOptions": {
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true
  }
}

 이 설정은 Node.js 스타일의 모듈 해석을 사용하고, default import를 허용합니다.

Node.js에서의 상호 운용성

 Node.js 13.2.0 이후 버전에서는 ES 모듈 지원이 추가되었습니다.

 .mjs 확장자를 사용하거나 package.json"type": "module"을 추가하여 ES 모듈을 사용할 수 있습니다.

package.json
{
  "type": "module"
}

점진적 마이그레이션 전략

  1. allowJs 옵션 활성화 : JavaScript와 TypeScript 파일 혼용
  2. esModuleInterop 옵션 활성화 : 임포트 호환성 개선
  3. 파일 단위로 ES 모듈 문법으로 변환
  4. 의존성 그래프의 말단부터 변환 시작
  5. 테스트 커버리지 유지 및 점진적 리팩토링

Best Practices와 주의사항

  1. 일관성 유지 : 가능한 한 프로젝트 내에서 하나의 모듈 시스템 사용
  2. 타입 정의 제공 : 특히 CommonJS 모듈에 대한 타입 정의 (.d.ts) 파일 작성
  3. 동적 임포트 주의 : 성능과 코드 분할을 고려하여 사용
  4. 순환 의존성 주의 : 특히 CommonJS와 ES 모듈 간 순환 의존성 발생 가능
  5. 번들러 설정 확인 : Webpack, Rollup 등의 번들러 설정 시 모듈 시스템 호환성 고려
  6. Node.js 버전 확인 : 사용 중인 Node.js 버전의 ES 모듈 지원 여부 확인
  7. 패키지 의존성 주의 : 서드파티 패키지의 모듈 시스템 호환성 확인
  8. 명시적 파일 확장자 : 임포트 시 파일 확장자 명시적 사용 (.js, .ts)
  9. Source Map 활용 : 디버깅을 위해 Source Map 생성 옵션 활성화
  10. 점진적 접근 : 대규모 프로젝트의 경우 점진적 마이그레이션 전략 채택

 CommonJS와 ES 모듈의 상호 운용성은 현대 JavaScript와 TypeScript 개발에서 중요한 주제입니다.

 두 시스템 간의 차이를 이해하고, 적절한 도구와 설정을 사용하여 호환성 문제를 해결하는 것이 핵심입니다.

 특히 레거시 코드베이스를 유지보수하거나 새로운 프로젝트를 시작할 때, 모듈 시스템의 선택과 상호 운용성 전략은 신중히 고려해야 할 사항입니다.

 타입스크립트는 이 두 시스템 사이의 가교 역할을 훌륭히 수행합니다.

 esModuleInterop과 같은 컴파일러 옵션을 통해 두 시스템 간의 호환성을 크게 향상시킬 수 있습니다.

 또한 타입 정의 파일(.d.ts)을 통해 CommonJS 모듈에 대한 타입 안정성도 제공할 수 있습니다.

 점진적 마이그레이션 전략은 대규모 프로젝트에서 특히 중요합니다.

 기존의 CommonJS 코드베이스를 ES 모듈로 전환할 때, 전체 시스템을 한 번에 변경하는 것은 위험할 수 있습니다.

 대신, 개별 모듈이나 기능 단위로 점진적으로 전환하면서 지속적인 테스팅과 통합을 수행하는 것이 안전한 접근 방법입니다.