안녕하세요. 이번 시간에는 자바스크립트의 다양한 모듈 시스템에 대해 알아보겠습니다. 비록 2018년 지금은 브라우저에는 ES2015 모듈(IE는 안 됩니다...), 노드에는 CommonJS 모듈(실험적인 ES2015 모듈 지원과 함께)로 통일되었지만, 아직도 많은 코드들이 AMD나 UMD, Common.js를 사용하고 있습니다.
그래서 AMD와 UMD에 대해 자세하게 알아보겠습니다. CommonJS와의 차이점도 알아보고요.
기존
일단, 모듈같은 것은 다른 사람의 코드나, 내가 잘게 쪼개 놓은 코드를 재사용하고 싶을 때 쓰게 됩니다. 예전에는 다른 사람의 코드가 있었다면
// 남의 코드들 먼저 불러오기
<script src="jquery.js"></script>
<script src="tweenmax.js"></script>
// 그걸 사용해 내 코드 작성
<script>
window.$
window.TweenMax
</script>
이런 식으로 불러오고자 하는 코드를 먼저 추가한 후, 다음에 내 스크립트에서 window에 추가된 $나 TweenMax 등을 사용하는 식이었습니다. 문제는 남의 코드들이 같은 변수를 사용할 때입니다.
<script src="jquery.js"></script>
<script src="zepto.js"></script>
<script>
window.$ // ???
</script>
jquery도 $ 변수를 쓰고, zepto도 $ 변수를 쓰기 때문에 변수 충돌이 일어납니다. 물론 변수를 다른 것으로 바꿀 수 있는 기능이 있긴 하지만, 일일이 충돌이 나는지 체크하는 게 귀찮습니다.
<script src="jquery.js"></script>
<script src="tweenmax.js"></script>
<script src="zerocho.js"></script>
<script>
window.$
window.TweenMax
window.zerocho // 쓸데 없는데 window에 추가됨
</script>
<script>
window.zerocho
</script>
위와 같은 경우도 생각해봅시다. 첫 번째 내 스크립트에서는 $와 TweenMax만 쓰고, 두 번째 내 스크립트에서는 zerocho라는 변수를 쓰는데요. 스크립트들을 다 위에 불러오다보니 첫 번째 스크립트에서도 zerocho가 접근이 됩니다. 쓸 데 없는 변수인데 접근이 되다보니 충돌의 위험이 더 커지는 것이죠.
내가 쓸 남의 코드만 미리 지정해서 그것들을 사용할 수 있으면 좋겠습니다. 안 쓸 코드들은 내 코드에서 배제하고요. 그리고 내 코드를 남이 쓸 때도 충돌같은 문제가 발생하지 않았으면 합니다. 이러한 문제들을 해결하기 위해 사람들은 다양한 해결 방법을 고안하기 시작했습니다. 그 중 유명한 것이 AMD와 CommonJS입니다.
AMD
AMD는 Asynchronous Module Definition으로 비동기적 모듈 선언이란 뜻입니다. 이를 구현한 가장 유명한 스크립트가 RequireJS 이고요.
<script src="require.js"></script>
먼저 require.js를 다운로드받아 스크립트로 넣어두시고요. 이제 코드를 작성하면 됩니다.
myModule.js
define(['jquery', 'zerocho'], function($, Z) {
console.log($);
console.log(Z);
return {
a: $,
b: Z,
}
});
이렇게 내가 쓰고자 하는 남의 코드들을 define의 첫 번째 인자 배열에 나열한 후, 두 번째 인자인 콜백 함수에서 매개변수로 받으면 됩니다. jquery는 $로, zerocho는 Z로 접근할 수 있습니다. $나 Z는 다른 변수로 바꿔도 되는 것 아시죠? 매개변수이니까요.
return한 부분은 남들이 가져다 쓸 수 있습니다. 모듈이 되는 셈입니다. 아래 예시를 보시죠.
만든 myModule.js를 require해서 쓰면 됩니다.
require(['myModule', 'TweenMax'], function (my, T) {
console.log(my.a); // jquery
console.log(my.b); // zerocho
console.log(T); // TweenMax
console.log(jquery); // undefined 또는 에러
});
이렇게 나 또는 남들도 내 모듈을 가져다 쓸 수 있습니다. 사용하는 모듈(또는 남의 스크립트)을 명시적으로 알려주고, 사용하지 않는 것들은 접근되지 않게 하기 때문에 유용합니다.
AMD에 대한 더 자세한 설명은 여기 D2 링크 에 나와 있습니다.
CommonJS
이 방식은 노드에서 채택한 방식으로 매우 유명합니다. 노드 강좌에서도 다뤘습니다. 보통 서버사이드에서는 CommonJS를 더 자주 씁니다. 브라우저에서는 AMD쪽이 더 좋아 보입니다(CommonJS도 브라우저에서 쓸 때는 AMD와 유사한 모양이 됩니다). 간단히 보여드리자면
myModule.js
const $ = require('jquery');
const Z = require('zerocho');
module.exports = {
a: $,
b: Z,
};
require(스크립트)로 불러와서 module.exports로 모듈화시키고 싶은 변수만 묶어주면 됩니다.
AMD랑 비교해서 보세요. 저는 노드 기반으로 프로그래밍을 하다보니 CommonJS 가 더 친숙하네요.
const my = require('myModule');
const T = require('TweenMax');
console.log(my.a, my.b);
console.log(T);
UMD
모듈 시스템을 개발한 것 까지는 좋은데 문제는 다양한... 이라는 것이죠. AMD와 CommonJS를 쓰는 두 그룹으로 나누어지다보니 서로 호환이 안 되게 되었습니다. 그래서 나온 것이 UMD입니다. 어떤 모듈을 쓰든지 동작되게 하기 위한 것이죠.
UMD는 하나로 정해진 코드라기 보다는 디자인 패턴에 더 가깝습니다. AMD, CommonJS, 그리고 기존처럼 window에 추가하는 방식까지 모든 경우를 커버할 수 있는 모듈을 작성하는 것이죠.
AMD는 define을 쓰고, CommonJS는 module.exports를 씁니다. 이 차이를 활용하면 UMD를 만들 수 있습니다. 모듈을 아래와 같이 선언하면 됩니다.
myModule.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) { // AMD
define(['jquery', 'zerocho'], factory);
} else if (typeof module === 'object' && module.exports) { // CommonJS
module.exports = factory(require('jquery'), require('zerocho'));
} else { // window
root.myModule = factory(root.$, root.Z);
}
}(this, function($, Z) {
return {
a: $,
b: Z,
};
});
AMD와 CommonJS에서 만든 myModule.js와 비교해보세요. AMD인 경우와, CommonJS인 경우 모두 즉시 실행함수 덕분에 factory 부분이 각자의 콜백 함수 또는 모듈 객체가 됩니다.
둘 다 아닌 경우(window)의 경우는 this가 window이기 때문에 root도 window가 되어, window.myModule에 값이 담기게 됩니다.
이제 어떠한 환경에서도 myModule.js를 불러오면 모듈로 만들었던 { a: $, b: Z }
를 사용할 수 있습니다.
이상으로 AMD, UMD, common.js에 대해 알아보았습니다. 다른 사람의 코드를 효율적으로 쓰기 위한 눈물겨운 노력들이었습니다! 제 포스트는 맛보기이기 때문에 추가적인 링크를 두 개 더 남겨놓겠습니다.