UMD(Universal Module Definition) 패턴
모듈이란 1개 이상의 값(객체, 함수, 변수 등)을 내보내는 자바스크립트 파일을 말한다. 모던 웹 개발에서는 자바스크립트가 차지하는 비중이 무척 높아졌기 때문에 모듈을 통한 기능과 관심사의 분리는 필수적이다. 자바스크립트 모듈 선언 포맷에는 AMD와 CommonJS 가 있다.
AMD
AMD(Asynchronous Module Definition)는 코드 모듈을 선언하면서 그것이 의존하고 있는 모듈도 함께 명시한다. 그리고 필요에 따라 비동기적으로 의존 모듈을 불러온다. AMD는 아래와 같은 형식으로 모듈을 선언한다.
define(['jquery'], function ($) { // 의존 모듈로 jQuery를 선언
// 함수 선언
function withjQuery(){};
// 외부에 함수를 노출시켜 사용할 수 있도록 한다.
return withjQuery;
});
AMD를 사용하면 실행할 코드가 필요한 파일만 가져오기 때문에 성능 향상을 기대할 수 있다. 그리고 의존하고 있는 모듈을 확실히 가져온 다음에 코드를 실행할 수 있기 때문에 오류도 줄어든다. AMD 포맷을 구현한 대표적인 라이브러리에는 Require.js, Dojo Toolkit 등이 있다.
CommonJS
CommonJS는 웹 브라우저가 아닌 환경에서의 자바스크립트 모듈 포맷을 확립하기 위한 프로젝트다. 이 프로젝트가 시작된 가장 큰 이유는 서로 다른 개발 환경(웹 서버, 네이티브 데스크탑 앱 등)에서 만들어진 자바스크립트 코드를 공유하기 위한 포맷이 없었기 때문이었다. 2009년 ServerJS라는 이름의 프로젝트로 시작되었고 이후 CommonJS로 이름이 바뀌게 된다. 자바스크립트의 표준이 되는 ECMAScript를 정의하는 ECMA 국제 그룹 TC39와 직접적인 관계는 없지만, TC39의 멤버 중 일부는 CommonJS 프로젝트에 참여하기도 했다.
CommonJS 포맷은 Node.js에서 구현해 뒀기 때문에 AMD보다 접해본 사람이 더 많을 것이다. 앞서 작성한 withJquery
모듈을 CommonJS 형식으로 작성하면 아래와 같다.
// 의존 모듈 선언
var $ = require('jquery');
// 함수 선언
function withjQuery(){};
// 외부에 함수를 노출시킨다.
module.exports = withjQuery;
UMD
앞에서 제시한 예제 코드를 통해 AMD와 CommonJS 포맷 모두 ‘의존 모듈을 정의하고 새로운 모듈을 내보낸다’는 같은 목적을 가지고 있다는 것을 알 수 있다. 다만 그 방식이 다를 뿐이다.
그런데 자바스크립트로 개발을 하다 보면 두 방식을 모두 지원해야 하는 상황이 생긴다. 대표적으로 오픈소스 라이브러리 모듈은 다양한 환경을 지원해야 할 필요가 있을 것이다. 그럴 때는 UMD(Universal Module Definition)를 사용해서 모듈을 선언할 수 있다.
UMD는 CommonJS처럼 스펙이 아닌 코드 작성 패턴이라고 보면 되며, 다양한 패턴이 존재한다. 아래의 패턴은 모듈을 AMD, CommonJS, 또는 브라우저 전역 변수로 할당해서 사용할 수 있도록 하는 returnExports.js 패턴이다.
// 모듈 선언을 위해 IIFE 패턴을 사용하고 있다.
// 이 예제에서는 의존 모듈을 표현하기 위해 임의로 모듈 'b'를 사용한다
// root 파라미터는 실행 환경에 따라 브라우저의 window 또는 node.js의 global을 가리킨다.
// factory 파라미터는 모듈 코드를 감싸고 있는 함수를 가리킨다.
(function (root, factory) {
// AMD에서 사용하는 define 함수를 사용할 수 있는지 확인한다.
if (typeof define === 'function' && define.amd) {
// AMD 포맷에 따라 모듈을 선언한다. 의존 모듈은 b가 된다.
define(['b'], factory);
// 의존 모듈이 없다면 아래와 같이 작성한다
// define([], factory);
// AMD를 지원하지 않는다면 CommonJS 모듈을 사용할 수 있는지 확인한다.
} else if (typeof module === 'object' && module.exports) {
// CommonJS 포맷에 따라 모듈을 선언한다.
// 의존 모듈을 factory 함수의 파라미터로 전달해준다.
module.exports = factory(require('b'));
// 의존 모듈이 없다면 아래와 같이 작성한다
// module.exports = factory();
} else {
// AMD도 CommonJS도 아니라면 브라우저라고 판단한다.
// 여기서 root는 window가 된다. returnExports는 전역에서 모듈의 이름이다.
root.returnExports = factory(root.b);
// 의존 모듈이 없다면 아래와 같이 작성한다
// root.returnExports = factory();
}
// 함수를 즉시 실행한다.
// 첫번째 파라미터는 전역 변수.
// (여기서는 Web Worker 지원을 위해 self 변수를 확인하지만 그럴 필요가 없다면 this만 전달해도 된다)
// 두번째 파라미터는 factory에 해당한다.
}(typeof self !== 'undefined' ? self : this, function (b) { // 의존 모듈 b
// return하는 값이 이 모듈이 내보내는 값이 된다(이 예제에서는 객체).
return {};
}));
코멘트를 제거하고 정리한 간략한 버전은 아래와 같다.
아래의 샘플은 현재 실행 환경을 확인하기위해 NODE_ENV
환경변수가 development
라면 true
, 아니면 false
를 반환하는 기능을 한다.
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory();
} else {
// browser
root.isDev = factory();
}
})(this, function() {
return process.env.NODE_ENV === 'development';
});
Github umdjs/umd 저장소에는 이 외에도 몇가지 패턴들을 더 제공하고 있으니 필요한 패턴을 참조해서 사용하면 될 것이다. 그리고 소스 파일을 UMD 패턴으로 변환시켜주는 도구(ex. gulp-umd)도 존재한다.