반디북
자바스크립트 + 리액트 디자인 패턴
Ch10
도은

모듈형이란, 서로 의존성이 낮은 기능들이 모듈로써 저장된 형태를 의미 이번 장에서는 ES5 문법을 사용하는 AMD, CommonJS, UMD 방식을 살펴본다.

10.1 스크립트 로더(script loader)

  • 자바스크립트 파일을 웹페이지에 동적으로 로드하거나 제어할 수 있도록 해주는 도구 또는 메커니즘
  • 모듈 로더(module loader)라고 부르기도 하는 듯
  • 의존성 주입과 모듈화를 함께 처리

🤔 스크립트 로더는 왜 필요한 것?

  • 브라우저가 지원하는 모듈 포맷과, 우리가 로드하려는 스크립트의 포맷이 다르기 때문
  • 브라우저는 import/export가 있는 ESM만 이해
    • 지원하지 않는 AMD, CJS을 냅다 사용하면
    • 브라우저는 이걸 실행하지 못함 (require is not defined)
  • 스크립트 로더는
    • 스크립트를 로드하고
    • 내부 포멧을 확인한 뒤에 (CJS인지 AMD인지)
    • 이를 브라우저에서 쓸 수 있는 방식으로 감싸서 잘 실행시켜주게 된다.

10.2 AMD

  • 모듈과 의존성 모두를 비동기적으로 로드할 수 있도록 설계된 모듈 정의 방식
  • 비동기적이면서도 높은 유연성 → 개발 과정에서 흔히 발생하는 코드와 모듈 간 긴밀한 결합을 줄여주는 등의 장점

모듈 알아보기

  • define

    • 이름이 있는 모듈 혹은 익명 모듈을 정의하는 데 사용
      define(
          module_id, /* 선택 인자 */
          [dependencies], /* 선택 인자 */
          definition function {} /* 모듈이나 객체를 인스턴스화하는 함수 */
      )
  • require

    • 최상위 자바스크립트 파일이나 모듈 내에서 의존성을 동적으로 가져오고자 할 때 사용

      // "foo"와 "bar"를 외부 모듈이라고 가정
      // 이 예제에서는 외부에서 로드된 두 모듈이 콜백 함수의 인자로 전달
      require(['foo', 'bar'], function (foo, bar) {
        // 나머지 코드
        foo.doSomething();
      });
       
      // 🔘 동적으로 로도딘 의존성
      define(function (require) {
        var isReady = false,
          foobar;
       
        // 모듈 내부에서 require를 통해 의존성을 가져온다
        require(['foo', 'bar'], function (foo, bar) {
          isReady = true;
          foobar = foo() + bar();
        });
       
        // 이렇게 ㄹ모듈을 반환
        return {
          isReady: isReady,
          foobar: foobar,
        };
      });

10.3 CommonJS

  • 서버 사이드에서 모듈을 선언하는 간단한 API를 지정하는 모듈 제안
  • AMD와 달리 I/O, 파일 시스템, 프로미스 등 더욱 광범위한 범위

CommonJS 시작하기

  • AMD와 달리 CJS는 모듈을 함수로 감싸는 작업이 필요하지 않다.
  • exports 변수는 다른 모듈에 내보내고자 하는 객체를 담는다.
  • require 함수는 다른 모듈에서 내보낸 객체를 가져올 때 사용하는 함수다.
// `package/lib` 외부 라이브러리를 가져온다.
var lib = require('package/lib');
 
// 모듈 내부 로직을 정의
function foo() {
  lib.log('hello world');
}
// foo 함수를 다른 모듈에서 사용할 수 있도록 내보낸다.
exports.foo = foo;

👇 위 코드를 AMD로

define(function (require) {
  var lib = require('package/lib');
 
  function foo() {
    lib.log('hello world');
  }
 
  return {
    foobar: foo,
  };
});

10.4 UMD

define(function (require, exports, module) {
  var shuffler = require('lib/shuffle');
  exports.randomize = function (input) {
    return shuffler.shuffle(input);
  };
});
  • 모듈에 의존성 배열이 없고, 정의 함수가 최소한 하나 이상의 매개변수를 가질 때에만 CommonJS 모듈로써 작동한다는 점을 유의

궁금한 점 이것저것

🤔 Node.js에서 require로 esm 모듈을 로드할 수 있는 기능을 제공

https://socket.dev/blog/require-esm-backported-to-node-js-20 (opens in a new tab)

  • Node.js에서 require로 ESM을 로드할 수 있도록 지원한다는 것은
    • 혼합 사용을 권장한다는 의미보다는 듀얼 포맷의 지원을 줄이고, ESM으로 통일해 나가려는 호환성 조치로 보인다.
    • 장기적으로는 새로운 모듈은 ESM으로만 작성되고, 점차 CJS를 줄이려는 방향으로 보이기도?

🤔 CJS vs ESM 성능차이?

세민

AMD

  • 모듈과 의존성 모두를 비동기적으로 로드할 수 있는 방식
    • 높은 유연성
    • 코드간 느슨한 결합
  • Dojo, MooTools, jQuery 등과 같은 프로젝트에 도입

AMD 모듈 형태

  • 모듈 정의부: define 메서드
  • 의존성 로딩 처리부: require 메서드
define('myModule', ['dependency1', 'dependency2'], function (dep1, dep2) {
  let privateVar = 'private';
 
  // 모듈에 접근가능한 Public API
  return {
    publicMethod: function () {
      return dep1.someMethod() + ' ' + dep2.anotherMethod();
    },
    getPrivateVar: function () {
      return privateVar;
    },
  };
});
require(['myModule', 'anotherModule'], function (myModule, anotherModule) {
  // define 부에서 정의한 Public API 사용
  console.log(myModule.publicMethod());
  console.log(anotherModule.someFunction());
 
  // 에러 처리
  require.onError = function (err) {
    console.error('모듈 로드 실패:', err);
  };
});

RequireJS 와 curl.js 로 AMD 모듈 사용하기

// RequireJS
require(['app/myModule']);
 
// 모듈 사용부...
 
// curl.js
curl(['app/myModule']);
 
// 모듈 사용부...

jQuery에서 AMD 모듈 사용하기

// jQuery를 의존성으로 가지는 AMD 모듈 정의
define('jqueryModule', ['jquery'], function ($) {
  return {
    // jQuery를 사용하는 메서드
    createElement: function (selector) {
      return $(selector);
    },
 
    // jQuery 플러그인 확장
    initPlugin: function () {
      $.fn.myPlugin = function () {
        return this.each(function () {
          $(this).css('color', 'red');
        });
      };
    },
 
    // jQuery 이벤트 핸들링
    setupEvents: function () {
      $('.button').on('click', function () {
        console.log('Button clicked!');
      });
    },
  };
});
 
// jQuery 모듈 사용 예시
require(['jquery', 'jqueryModule'], function ($, jqueryModule) {
  // 모듈 초기화
  jqueryModule.initPlugin();
 
  // jQuery 플러그인 사용
  $('.element').myPlugin();
 
  // 이벤트 설정
  jqueryModule.setupEvents();
});
  • AMD 모듈 방식은 초기 작성해야하는 코드가 좀 있어서 귀찮은듯. 어느정도 형태에 익숙해지면 모듈로서 사용성이 좋다고 느껴짐.

CommonJS

  • Server Side 관점에서 모듈화
    • Node.js 환경에서 자바스크립트 코드를 패키징하기 위해 처음 등장
  • 재사용 가능한 자바스크립트 코드인 외부 의존에 공개할 특정 객체를 내보냄 (exports)
// foo.js
exports.bar = 'hello world';
 
// main.js
const foo = require('./foo');
console.log(foo.bar); // hello world

Node JS 환경과 브라우저 환경

Common JS는 항상 객체를 내보내기 때문에 조금 더 세밀한 방식으로 컨트롤 하는데는 ES2015, AMD가 더 유리할 수 있다. 또한 ES2015 모듈은 서버에서 Common JS 대안으로 사용이 가능하다.

  • require() 함수를 호출하면 항상 Common JS 모듈 로더
  • import() 함수를 호출하면 항상 ECMAScript 모듈 로더

이는 package.json 파일에 설정된 type과 상관 없이 항상 적용된다.

UMD

  • AMD와 CommonJS를 모두 지원하는 모듈 시스템
  • 브라우저와 Node.js 환경 모두에서 동작하도록 설계
  • 모듈 시스템을 감지하여 적절한 방식으로 동작
// UMD 패턴 예시
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD 환경
    define(['jquery'], factory);
  } else if (typeof exports === 'object') {
    // CommonJS 환경
    module.exports = factory(require('jquery'));
  } else {
    // 브라우저 전역 환경
    root.myModule = factory(root.jQuery);
  }
})(this, function ($) {
  // 모듈 구현부
  return {
    sayHello: function () {
      console.log('Hello from UMD module!');
    },
  };
});

UMD의 주요 특징

  1. 환경 감지

    • AMD 환경: define 함수 존재 여부 확인
    • CommonJS 환경: exports 객체 존재 여부 확인
    • 브라우저 환경: 전역 객체에 모듈 노출
  2. 장점

    • 다양한 환경에서 동작 가능
    • 기존 모듈 시스템과의 호환성
    • 라이브러리 배포에 적합
  3. 단점

    • 코드가 복잡해질 수 있음
    • 번들러 사용 시 불필요한 코드가 포함될 수 있음