icon안동민 개발노트

ES 모듈 시스템


 ES 모듈 시스템은 JavaScript에서 코드를 모듈화하고 재사용하기 위한 표준 방식입니다.

 이는 기존의 CommonJS나 AMD와 같은 모듈 시스템과 달리 언어 자체에 내장된 기능입니다.

ES 모듈 vs CommonJS

 주요 차이점

  1. 문법 : ES 모듈은 import/export 키워드를 사용, CommonJS는 require()/module.exports를 사용
  2. 로딩 : ES 모듈은 정적 분석이 가능한 반면, CommonJS는 동적 로딩
  3. 비동기성 : ES 모듈은 비동기 로딩을 지원

Export 문

  1. Named Export
export const PI = 3.14159;
export function square(x: number) {
    return x * x;
}
  1. Default Export
export default class Circle {
    constructor(public radius: number) {}
    area() {
        return Math.PI * this.radius ** 2;
    }
}

 Named export는 여러 개의 값을 내보낼 때 유용하고, default export는 모듈당 하나의 주요 기능을 내보낼 때 사용합니다.

Import 문

  1. Named Import
import { PI, square } from './math';
console.log(square(PI));
  1. Default Import
import Circle from './circle';
const circle = new Circle(5);
  1. Namespace Import
import * as Math from './math';
console.log(Math.square(Math.PI));

 Namespace import는 모듈의 모든 exports를 하나의 객체로 가져올 때 유용합니다.

재내보내기 (Re-export)

 다른 모듈의 기능을 현재 모듈을 통해 다시 내보낼 수 있습니다.

export { square } from './math';
export { default as Circle } from './circle';

 이 방식은 여러 모듈의 기능을 하나의 진입점으로 모을 때 유용합니다.

동적 임포트 (Dynamic Import)

 런타임에 모듈을 동적으로 로드할 수 있습니다.

async function loadModule() {
    const { default: Module } = await import('./dynamicModule');
    const instance = new Module();
    // 사용...
}

 이 기능은 코드 스플리팅과 지연 로딩에 유용합니다.

순환 의존성 (Circular Dependency)

 순환 의존성은 두 모듈이 서로를 import할 때 발생합니다.

 이를 해결하기 위한 방법

  1. 모듈 구조 재설계
  2. 인터페이스를 통한 의존성 분리
  3. 동적 import 사용
// moduleA.ts
import { funcB } from './moduleB';
export function funcA() {
    console.log('Function A');
    funcB();
}
 
// moduleB.ts
import { funcA } from './moduleA';
export function funcB() {
    console.log('Function B');
    // funcA를 직접 호출하지 않고 필요할 때 동적으로 import
    import('./moduleA').then(module => module.funcA());
}

타입스크립트에서의 모듈 타입 정의

 타입스크립트에서는 .d.ts 파일을 사용하여 모듈의 타입을 정의합니다.

// math.d.ts
export const PI: number;
export function square(x: number): number;

 이러한 타입 정의는 코드의 자동 완성과 타입 검사에 중요합니다.

ES 모듈과 CommonJS의 상호 운용성

 타입스크립트는 ES 모듈과 CommonJS 모듈 간의 상호 운용성을 지원합니다.

// CommonJS 모듈 import
import * as fs from 'fs';
 
// ES 모듈 style로 CommonJS 모듈 import
import { readFile } from 'fs';

 tsconfig.json에서 "esModuleInterop": true를 설정하면 더 나은 호환성을 제공합니다.

모듈 번들러와 최적화

 모듈 번들러(예: Webpack, Rollup)는 ES 모듈을 브라우저에서 효율적으로 실행할 수 있는 형태로 변환합니다.

 최적화 전략

  1. Tree Shaking : 사용하지 않는 코드 제거
  2. Code Splitting : 필요한 코드만 로드
  3. Lazy Loading : 필요할 때 모듈 동적 로드
// Webpack 설정 예시
module.exports = {
    entry: './src/index.ts',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js']
    }
};

Best Practices와 모듈 구조 설계

  1. 단일 책임 원칙 : 각 모듈은 하나의 책임만 가지도록 설계
  2. 명확한 public API : 모듈의 public API를 명확히 정의
  3. 의존성 최소화 : 모듈 간 의존성을 최소화하여 결합도 낮추기
  4. 순환 의존성 피하기 : 모듈 간 순환 의존성을 피하도록 설계
  5. 일관된 명명 규칙 : 모듈과 export 이름에 일관된 규칙 적용
  6. 타입 정의 제공 : 모든 모듈에 대해 타입 정의 (.d.ts) 제공
  7. 재사용성 고려 : 범용적으로 사용 가능한 모듈 설계
  8. 버전 관리 : 모듈의 버전을 명확히 관리
  9. 문서화 : 각 모듈의 사용 방법과 API 문서화
  10. 테스트 작성 : 각 모듈에 대한 단위 테스트 제공

 대규모 애플리케이션에서의 모듈 구조 설계

  1. 기능별 모듈화 : 애플리케이션의 주요 기능별로 모듈 구성
  2. 레이어 아키텍처 : 데이터, 비즈니스 로직, UI 등 레이어별 모듈 구성
  3. 공유 모듈 : 여러 부분에서 사용되는 기능을 공유 모듈로 분리
  4. 핵심/플러그인 구조 : 핵심 기능과 확장 기능을 분리하여 모듈화
  5. 동적 로딩 : 필요한 모듈만 동적으로 로드하는 구조 설계
// 애플리케이션 구조 예시
src/
  ├── core/
  │   ├── types.ts
  │   ├── utils.ts
  │   └── constants.ts
  ├── data/
  │   ├── api.ts
  │   └── store.ts
  ├── features/
  │   ├── feature1/
  │   │   ├── index.ts
  │   │   ├── components/
  │   │   └── services/
  │   └── feature2/
  │       ├── index.ts
  │       ├── components/
  │       └── services/
  ├── shared/
  │   ├── components/
  │   └── hooks/
  └── index.ts

 ES 모듈 시스템은 현대 JavaScript와 TypeScript 개발의 핵심입니다.

 모듈 시스템의 기능을 충분히 이해하고, 프로젝트의 요구사항에 맞게 적절히 적용하는 것이 중요합니다.

 또한 모듈 번들러와 최적화 도구를 함께 사용하여 생산성과 성능을 모두 고려한 개발을 진행해야 합니다.