icon안동민 개발노트

모듈 시스템 (CommonJS, ES Modules)


 자바스크립트 모듈 시스템은 코드의 재사용성, 유지보수성, 그리고 의존성 관리를 향상시키기 위해 발전해왔습니다.

 이 절에서는 주요 모듈 시스템인 CommonJS와 ES Modules에 대해 자세히 알아보겠습니다.

모듈 시스템의 필요성과 발전

 초기 자바스크립트는 모듈 시스템이 없어 전역 스코프 오염, 의존성 관리의 어려움 등의 문제가 있었습니다.

 이를 해결하기 위해 CommonJS(서버 사이드), AMD(브라우저), UMD(범용) 등의 모듈 시스템이 등장했고, 최종적으로 ES6에서 표준 모듈 시스템이 도입되었습니다.

CommonJS

 Node.js에서 주로 사용되는 모듈 시스템입니다.

 기본 문법

  1. 모듈 내보내기
// math.js
module.exports = {
   add: (a, b) => a + b,
   subtract: (a, b) => a - b
};
  1. 모듈 가져오기
// app.js
const math = require('./math');
console.log(math.add(5, 3)); // 8

 동작 원리

 CommonJS는 동기적으로 모듈을 불러옵니다.

 모듈이 처음 require될 때 실행되고 캐시되며, 이후 호출에서는 캐시된 모듈을 반환합니다.

ES Modules

 ECMAScript 6에서 도입된 표준 모듈 시스템입니다.

 기본 문법

  1. 모듈 내보내기
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
  1. 모듈 가져오기
// app.js
import { add, subtract } from './math.js';
console.log(add(5, 3)); // 8

 CommonJS와의 주요 차이점

  1. 문법 : require/module.exports vs import/export
  2. 로딩 : CommonJS는 동기적, ES Modules는 비동기적
  3. 정적 분석 : ES Modules는 import 문을 정적으로 분석 가능

정적 import vs 동적 import

 1. 정적 import

  • 모듈 최상위에서 사용
  • 빌드 시점에 분석 가능
import { add } from './math.js';

 2. 동적 import

  • 런타임에 모듈을 비동기적으로 로드
  • 조건부 모듈 로딩에 유용
if (condition) {
   import('./module.js').then(module => {
      // 모듈 사용
   });
}

모듈의 순환 참조

 모듈 A가 모듈 B를 참조하고, 동시에 B가 A를 참조하는 상황입니다.

// a.js
import { b } from './b.js';
export const a = 1;
console.log(b);
 
// b.js
import { a } from './a.js';
export const b = 2;
console.log(a);

 해결 방법

  1. 모듈 구조 재설계
  2. 동적 import 사용
  3. 공통 의존성을 별도 모듈로 분리

브라우저에서의 ES Modules

<script type="module" src="app.js"></script>

 주의사항

  • CORS 정책 준수 필요
  • 로컬 파일에서 직접 실행 불가 (웹 서버 필요)
  • 항상 엄격 모드로 실행됨

모듈 번들러

 모듈 번들러는 여러 모듈을 하나의 파일로 묶어주는 도구입니다.

 Webpack 기본 사용법

  1. 설치
npm install webpack webpack-cli --save-dev
  1. 설정 (webpack.config.js)
module.exports = {
   entry: './src/index.js',
   output: {
      filename: 'bundle.js',
      path: __dirname + '/dist'
   }
};
  1. 실행
npx webpack

대규모 앱의 모듈 시스템 활용 전략

  1. 모듈 구조화 : 기능별로 모듈을 분리하고, 명확한 책임을 가지도록 설계합니다.
  2. 느슨한 결합 : 모듈 간 의존성을 최소화하여 유지보수성을 높입니다.
  3. 동적 임포트 활용 : 필요한 시점에 모듈을 로드하여 초기 로딩 시간을 단축합니다.
  4. 트리 쉐이킹 : 사용하지 않는 코드를 제거하여 번들 크기를 줄입니다.
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
 
// app.js
import { add } from './math.js';  // subtract는 번들에 포함되지 않음
  1. 코드 스플리팅 : 애플리케이션을 여러 청크로 나누어 필요할 때 로드합니다.
import('./bigModule.js').then(module => {
   // 큰 모듈 사용
});
  1. 순환 의존성 방지 : 모듈 간 순환 참조를 피하고, 필요한 경우 리팩토링합니다.
  2. 명명 규칙 : 일관된 모듈 명명 규칙을 사용하여 가독성과 유지보수성을 높입니다.
  3. 문서화 : 각 모듈의 목적, 공개 API, 사용 예시 등을 문서화합니다.
  4. 테스트 : 각 모듈을 독립적으로 테스트 가능하도록 설계합니다.
  5. 버전 관리 : 외부 모듈의 경우 버전을 명시하여 예기치 않은 변경을 방지합니다.

 Best Practices

  • 모듈당 하나의 책임 원칙을 따릅니다.
  • 필요한 것만 임포트/익스포트하여 모듈 인터페이스를 깔끔하게 유지합니다.
  • 순수 함수와 불변 데이터 구조를 사용하여 모듈 간 부작용을 최소화합니다.
  • 공통 유틸리티 함수는 별도의 모듈로 분리하여 재사용성을 높입니다.
  • 환경 설정과 비즈니스 로직을 분리하여 모듈의 이식성을 높입니다.

 자바스크립트 모듈 시스템은 코드 구조화와 관리의 핵심입니다. CommonJS와 ES Modules는 각각의 장단점을 가지고 있으며, 사용 환경과 요구사항에 따라 적절히 선택해야 합니다.

 CommonJS는 Node.js 환경에서 널리 사용되며, 동기적 로딩과 간단한 문법이 특징입니다. 반면 ES Modules는 브라우저와 Node.js 모두에서 사용 가능하며, 정적 분석이 가능해 트리 쉐이킹 등의 최적화에 유리합니다.

 대규모 애플리케이션에서 모듈 시스템을 효과적으로 활용하려면, 모듈의 구조화부터 동적 임포트, 코드 스플리팅 등의 고급 기법까지 다양한 전략을 적용해야 합니다. 이를 통해 코드의 재사용성, 유지보수성, 그리고 성능을 크게 향상시킬 수 있습니다.

 모듈 번들러의 사용은 현대 웹 개발에서 필수적입니다. Webpack과 같은 도구를 통해 모듈을 효율적으로 관리하고, 최적화된 번들을 생성할 수 있습니다. 이는 특히 대규모 프로젝트에서 로딩 성능과 리소스 관리에 큰 도움이 됩니다.

 결론적으로, 모듈 시스템의 올바른 이해와 활용은 현대 자바스크립트 개발의 핵심 역량입니다. 이를 통해 더 유지보수가 쉽고, 확장 가능하며, 효율적인 애플리케이션을 구축할 수 있습니다.