Reese-log
  • About
  • Blog

© 2025 Reese. All rights reserved.

2023년 12월 2일

JavaScript Module System(1) - IIFE부터 CJS AMD까지

#Module

Module

모듈이란? - 애플리케이션을 구성하는 개별적 요소이자, 재사용 가능한 코드 조각을 말합니다.

모듈은

  • 기능을 기준으로 파일 단위로 분리하며
  • 자신만의 파일 스코프(모듈 스코프)를 가질 수 있어야 합니다.
  • IIFE를 사용해 모듈 패턴을 만들던 시절

    IIFE 즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression) 

    → 정의되자마자 즉시 실행되는 함수를 말합니다.

    *IIFE로 모듈 패턴 만들기 - 출처 IIFE라는 이름을 지은 Bem Almen의 블로그(2010년)

    javascript
    1var counter = (function(){
    2    var i = 0;
    3
    4    return {
    5      get: function(){
    6        return i;
    7      },
    8      set: function( val ){
    9        i = val;
    10      },
    11      increment: function() {
    12        return ++i;
    13      }
    14    };
    15  }());
    16
    17  // `counter` is an object with properties, which in this case happen to be
    18  // methods.
    19
    20  counter.get(); // 0
    21  counter.set( 3 );
    22  counter.increment(); // 4
    23  counter.increment(); // 5

    IIFE로 모듈을 만들어 사용함으로 얻었던 이점

  • 전역 스코프 오염 방지
  • 캡슐화
  • 모듈화
  • IIFE로 모듈을 만들어 썼을 때의 아쉬운점

  • 코드 가독성 - 많은 IIFE를 사용할 경우 코드가 난해해보입니다.
  • 전역 네임스페이스 문제 - 네임스페이스가 충돌하는 문제가 있었습니다.
  • 모듈간 의존성 관리가 어려움 - 의존성 주입을 수동으로 처리해야 했습니다.
  • 재사용성 제한 - 제한적인 재사용성을 갖고 있었습니다.

  • IIFE를 통한 모듈 패턴이 사용되던 시기에 JavaScript는 주로 브라우저 환경에서 사용되었는데요.

    서버 측 JavaScript 환경이 등장하면서(Node.js - 2009년 5월 27 처음 등장) 더 나은 모듈 시스템의 중요성이 더욱 커졌습니다. 이 때 등장한 것이 CommonJS 모듈 시스템입니다.

    CommonJS(CJS)

    JavaScript를 서버 측에서 사용할 수 있는 표준을 만들기 위해 모인 그룹 ServerJS에 의해 시작되었습니다.

    CommonJS는 동기적으로 모듈을 로드합니다. 서버 측 환경에서는 동기식 로딩이 자연스럽기 때문입니다.

    왜 그럴까요?

  • 초기화의 간단함
  • 명확한 실행 흐름
  • I/O 속도의 차이
  • 복잡한 의존성 관리
  • 문법

    require 함수로 모듈을 로드하고, module.exports 또는 exports객체로 모듈을 내보냅니다.

    javascript
    1//모듈 정의하기
    2const message = "Hello, CommonJS";
    3
    4function greet() {
    5    console.log(message);
    6}
    7
    8module.exports = {
    9    greet
    10};
    javascript
    //모듈 가져오기
    const example = require('./example');
    example.greet();

    특징

    파일 단위 모듈 - 각 파일이 하나의 모듈로 간주되며, 모듈 간의 의존성은 파일 시스템 경로로 관리됩니다.

    캐싱 - 모듈은 처음 로드될 때 한 번만 실행되고, 이후에는 캐시된 버전이 사용됩니다.

    장단점

    장점 - 단순한 문법, 동기식 로딩, 풍부한 생태계(npm)

    단점 - 클라이언트 측 환경에 부적합(동기식 로딩이어서)

    *CommonJS 모듈 시스템에서 Factory 패턴

    CommonJS 모듈 시스템에서 Factory 패턴은 module.exports를 통해 모듈을 정의하고, require 함수로 모듈을 로드할 때 사용됩니다. CommonJS에서는 각 파일이 자체적으로 하나의 모듈로 간주되며, module.exports는 모듈을 내보내는 Factory 역할을 합니다.

    javascript
    1var add = function(x, y) {
    2    return x + y;
    3};
    4
    5var subtract = function(x, y) {
    6    return x - y;
    7};
    8
    9module.exports = {
    10    add: add,
    11    subtract: subtract
    12};
    javascript
    var math = require('./math');
    
    console.log(math.add(2, 3));       // 출력: 5
    console.log(math.subtract(5, 2));  // 출력: 3

    브라우저 환경을 지원하는 비동기식 로딩 모듈시스템에 대한 필요성 대두되었습니다. ⇒ AMD의 등장


    AMD(Asynchronous Module Definition)

    비동기식 로딩을 지원하는 브라우저 환경에 적합한 모듈 시스템

    브라우저에서는 왜 비동기식 로딩이 적합한가요?

  • 페이지 로드 성능 향상
  • 사용자 경험 개선과 리소스 최적화
  • 네트워크 효율성
  • 문법

    RequireJS라는 AMD 규격을 따르는 모듈 로더 라이브러리를 사용합니다.

    *RequireJS는 파일 경로 매핑, 템플릿 로딩, 비동기적 의존성 로딩 등을 지원합니다.

    define 함수로 모듈을 정의하고, require 함수로 모듈을 로드하여 사용합니다.

    javascript
    1// math.js
    2define([], function() {
    3    var add = function(x, y) {
    4        return x + y;
    5    };
    6
    7    var subtract = function(x, y) {
    8        return x - y;
    9    };
    10
    11    return {
    12        add: add,
    13        subtract: subtract
    14    };
    15});
    javascript
    // main.js
    require(['math'], function(math) {
        console.log(math.add(2, 3));       
        console.log(math.subtract(5, 2)); 
    });

    장점

  • 비동기식 로딩으로 인한 성능 향상
  • 명시적인 의존성 선언과 관리
  • 모듈화된 코드 구조
  • 단점

    문법이 상대적으로 복잡하고 많은 함수 호출과 콜백이 필요해 코드가 장황해집니다.

    ! CommonJS 모듈과 직접 호환되지 않습니다. (호환성 문제)

    ⇒ 호환성 문제 해결을 위해 UMD 등장

    AMD와 factory pattern 톺아보기

    AMD 모듈 로더 라이브러리인 RequireJS의 define 함수

    define 함수는 다음과 같은 구조를 가집니다.

    javascript
    define(id, dependencies, factory);
  • id (optional): 모듈의 이름을 지정합니다. 생략하면 파일 이름이 모듈 이름으로 사용됩니다.
  • dependencies (opitional): 모듈이 의존하는 모듈들의 배열입니다. 생략하면 기본적으로 require, exports, module이 사용됩니다.
  • factory: 모듈의 실제 구현입니다. 함수나 객체를 반환합니다.
  • *AMD의 facotry패턴

    객체 생성의 책임을 팩토리 클래스나 메서드에 위임하는 디자인 패턴입니다. 객체 생성의 복잡성을 숨기고, 객체 생성 로직을 중앙 집중화합니다.

    define함수의 factory parameter

    모듈을 정의하고 반환하는 역할을 합니다. 함수는 주어진 의존성들을 받아 모듈의 인터페이스를 정의하고 이를 호출하는 코드에 반환합니다.

    javascript
    1// math.js 모듈 정의
    2define('math', [], function() {
    3    var add = function(x, y) {
    4        return x + y;
    5    };
    6
    7    var subtract = function(x, y) {
    8        return x - y;
    9    };
    10
    11    return {
    12        add: add,
    13        subtract: subtract
    14    };
    15});

    factory파라미터로 전달된 함수는 의존성 배열을 통해 모듈의 의존성을 주입받고, 모듈을 정의하여 반환합니다.

    이 함수는 팩토리 함수로서의 역할을 합니다. (의존성을 주입받아 모듈을 생성하고 이를 호출하는 코드에 반환합니다.) → 모듈 간의 의존성을 명확히 하고, 모듈의 재사용성과 유지보수성을 높이는데 기여합니다.

    AMD의 호환성 문제로 인해 UMD가 등장하는데… (이어서)