최근 라이브러리 개발을 진행하는데 최초 설정에서 알아두면 좋을 것 같은 모듈 시스템들에 대해서 아주 간단하게 알아보려고 합니다.
라이브러리를 개발할 때 보통 사용하는 ES6 Modules (ESM)와 CommonJS (CJS) 이외의 시스템들도 함께 알아보겠습니다.
CommonJS는 주로 Node.js에서 사용되는 모듈 시스템으로, 서버 측 자바스크립트에서 표준으로 사용됩니다.
- 동기 로딩: `require`와 `module.exports` 사용
- 파일 시스템과 네트워크 요청 등을 쉽게 처리 가능
- Node.js 환경에서 기본 지원
- Node.js와 긴밀하게 통합되어 있어 서버 측 코드에 적합
- 브라우저 환경에서는 직접 사용할 수 없고, 번들러가 필요
- 비동기 방식보다 느리고, 트리쉐이킹이 어렵습니다.
// math.js
exports.add = function(x, y) {
return x + y;
};
// main.js
const math = require('./math');
console.log(math.add(2, 3)); // 5
동기(Synchronous) 모듈 로딩
동기 모듈 로딩은 모듈이 즉시 로드되고 실행되는 방식입니다. 이는 코드가 순차적으로 실행된다는 것을 의미합니다. 즉, 하나의 작업이 완료된 후에 다음 작업이 시작됩니다.
비동기(Asynchronous) 모듈 로딩
비동기 모듈 로딩은 모듈이 비동기적으로 로드되고 실행되는 방식입니다. 이는 코드가 로드되거나 실행되는 동안 다른 작업을 수행할 수 있다는 것을 의미합니다. 비동기 로딩은 주로 웹 브라우저 환경에서 사용됩니다.
트리쉐이킹(tree shaking)
임포트 되었지만 실제로는 사용되지 않은 코드를 분석하고 삭제하는 코드 최적화 기술
AMD는 주로 브라우저 환경에서 비동기적으로 모듈을 로드할 때 사용되며, RequireJS와 함께 주로 사용됩니다.
- `define`과 `require` 함수를 사용
- 비동기 로딩을 통해 성능 향상
- RequireJS와 같은 라이브러리에서 주로 사용
- 브라우저에서 동적으로 모듈을 로드하고 의존성을 관리해야 할 때 유용
- 서버 측 환경에서는 적합하지 않음
- 코드 가독성이 떨어질 수 있음
// 모듈 정의
define([
'jquery',
'underscore',
// 의존 모듈들을 배열로 나열
], function ($, _) {
// 의존 모듈들은 순서대로 매개변수에 담김
return {
// 외부에 노출할 함수들만 반환
};
});
// 모듈 사용
require([
...
// 사용할 모듈 배열로 나열
], function (...) {
// 사용할 모듈들이 순서대로 매개변수에 담김
});
UMD는 모듈이 브라우저와 Node.js 환경 모두에서 동작할 수 있도록 설계된 유니버설 모듈 포맷입니다.
CommonJS와 AMD 방식을 모두 호환할 수 있도록 조건문으로 분기하고, 이를 팩토리 패턴으로 구현하였으며, 전역 객체(브라우저 환경에서는 window)를 지원합니다.
- AMD와 CommonJS를 모두 지원
- 브라우저 글로벌 변수로도 사용 가능
- 유연한 구조
- 브라우저, Node.js 등 다양한 환경에서 사용할 수 있는 라이브러리를 개발할 때 유용
- 코드가 복잡해질 수 있음
- 최신 표준을 따르지 않음
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD 방식
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// CommonJS 방식
module.exports = factory(require('jquery'), require('underscore'));
} else {
root.foo = factory(root.$, root._);
}
})(this, function ($, _) {
// 모듈 정의
var foo = {
// ...
};
return foo;
});
ECMAScript Modules는 ES6(ES2015)에서 도입된 표준 모듈 시스템으로, 최신 브라우저와 Node.js 환경에서 사용됩니다.
- 정적 로딩: `import`와 `export` 키워드 사용
- 트리 쉐이킹(Tree Shaking) 지원: 사용하지 않는 코드를 자동으로 제거하는 기능
- 지원 도구: 대부분의 최신 빌드 도구(Webpack, Rollup 등)에서 기본 지원
- 브라우저 및 Node.js 환경 모두에서 사용 가능
- 최신 자바스크립트 기능과 호환 가능
- 구형 브라우저 지원 문제(폴리필 필요)
- 개발 환경 설정이 필요할 수 있습니다.
// math.js
export function add(x, y) {
return x + y;
}
// main.js
import { add } from './math.js';
console.log(add(2, 3)); // 5
IIFE는 즉시 실행 함수 표현식으로, IFE 자체는 모듈 시스템은 아니지만, 자바스크립트의 모듈 시스템이 도입되기 이전에 전역 네임스페이스 오염을 방지하고 자바스크립트 코드를 구조화 및 독립된 스코프에서 실행하기 위한 패턴으로 널리 사용되었습니다.
IIFE는 정의되자마자 즉시 실행되는 함수 표현식입니다. 이는 함수를 정의하면서 동시에 호출함으로써, 할 수 있습니다.
- 간단하고 정의되자마자 즉시 실행
- UMD와 함께 많이 사용
- 함수 내부의 스코프를 외부와 분리하고, 변수 충돌을 방지
- 브라우저 환경에서 단일 파일로 모듈을 정의하고 사용할 때 유용
- 주로 초기화 코드나 라이브러리에서 사용
- 모듈화 기능이 부족하며, 코드 재사용성이 낮습니다.
(function() {
// 내부에 정의된 변수는 외부에 영향을 미치지 않습니다.
var privateVariable = 'This is a private variable';
console.log(privateVariable);
})();
// privateVariable은 외부 스코프에서 접근할 수 없습니다.
console.log(typeof privateVariable); // undefined
위의 모듈들은 각각의 장점과 사용 목적에 따라 선택할 수 있습니다.
최신 브라우저와 Node.js 환경에서 사용하기 위해서는 ESM을 고려하고,
서버 측 자바스크립트에서는 CommonJS를,
다양한 환경에서 호환성을 고려한다면 UMD를 사용하는 것이 좋습니다.
AMD는 브라우저에서의 비동기 로딩이 필요한 경우에 적합하며,
IIFE는 간단한 초기화 코드나 작은 유틸리티 라이브러리에 적합합니다.
이 처럼 라이브러리를 제작할 때 모듈 시스템을 선택하는 것은 라이브러리의 목적, 사용 환경, 호환성, 요구 사항 등을 고려하여 결정해야 합니다.
[ JavaScript ] CJS, AMD, UMD, ESM
JavaScript 번들러로 본 조선시대 붕당의 이해