[JavaScript] 번들러와 모듈 시스템 역사
자바스크립트 초기에는 간단한 모듈 시스템만을 제공했다.
<html>
<script src="/src/foo.js"></script>
<script src="/src/bar.js"></script>
</html>
// foo.js
var todo = "코딩하기"
// bar.js
var todo = "꿀잠자기"
위 두 스크립트를 로드한 전역 컨텍스트에서 각 모듈 간의 충돌이 발생한다는 문제가 있었다. foo.js에서 선언한 변수와 같은 이름을 가진 변수가 bar.js에 선언되어 있다면 나중에 호출된 bar.js의 변수로 재정의 되면서 foo.js의 파일이 제대로 동작하지 않게 된다.
즉, 모듈 간의 스코프가 구분되지 않아서 다른 파일을 오염시키는 경우가 발생했다.
이런 문제점들을 갖고 있는 상태로 2008년 Google의 V8 엔진이 공개되었고, 이를 이용해 서버 사이드에서 자바스크립트를 활용하자는 아이디어가 제시 되었다. 그래서 더더욱 모듈화가 필요해졌고 결국 2009년 자바스크립트 모듈을 표준화하기 위해 CommonJS와 AMD(Asynchronous Module Definition)가 등장했다.
CommonJS
// CommonJS
// 모듈 정의
module.exports = foo;
// 모듈 사용
const foo = require("./foo");
CommonJS는 브라우저 외의 환경에서도 동작하는 범용적은 모듈 시스템이기 때문에 모든 디펜던시가 로컬 디스크에 존재해서 필요한 모듈을 바로 사용할 수 있는 환경을 전제로 한다. 따라서 동기적으로 모듈을 호출하는 방식을 선택했다.
Node.js는 CommonJS의 방식의 명세를 채택하고 구현했다. npm의 성장과 함께 자바스크립트의 생태계의 규모는 폭발적으로 증가하게 된다. 하지만 처음부터 비동기 로드를 고려하지 않은 설계 때문에 브라우저에서는 CommonJS를 사용할 수 없다는 문제가 있었다.
이때 비동기적으로 자바스크립트 모듈을 사용하고 싶은 그룹이 생겨났고, CommonJS와 합의점을 찾지 못하고 독립한 그룹이 바로 AMD(Asynchronous Module Definition)이다.
AMD (Asynchronous Module Definition)
// AMD
// 모듈 정의
define([
// 의존 모듈들을 배열로 나열
'jqeury',
'underscore',
], function ($, _) {
// 의존 모듈들은 순서대로 매개변수에 담김
return {
// 외부에 노출할 함수들만 반환
};
});
// 모듈 사용
require([
...
// 사용할 모듈 배열로 나열
], function (...) {
// 사용할 모듈들이 순서대로 매개변수에 담김
});
복잡한 문법이 눈에 띄긴 하지만 비동기적으로 모듈을 호출하는 특성 때문에 퍼포먼스 면에서는 CommonJS보다 나은 성능을 보였다. 그리고 브라우저와 서버사이드 모두 호환되는 방식이었고, AMD 명세로 구현된 대표적인 모듈 라이브러리는 RequireJS가 있다.
UMD (Universal Module Definition)
CommonJS와 AMD는 서로 지향하는 목적이 달랐기 때문에 통일되지 않은 규격들이 발생했고 이는 호환성 문제로 이어졌다.
이때 호환성 문제를 해결하기 위해 등장한 것이 UMD이다. 두 방식을 모두 호환하면서 window객체를 통해 전역적으로도 접근할 수 있다. 웹팩과 롤업 같은 몇몇 자바스크립트 번들러들은 ES6 방식으로 모듈 로드에 실패했을 때, 대안책으로 UMD 패턴으로 로드하는 방식을 아직도 사용한다고 한다.
ES6 Module
2015년 자바스크립트 언어 자체에서 모듈시스템을 지원하는 ECMAScript6사양인 ES6 Module이 등장한다.
// ES6
import foo = from "bar";
export default qux;
ES6 Module은 그동안에 나왔던 문제들을 해결하기 위해 많은 노력을 기울였다.
- 동기 / 비동기 로드를 모두 지원
- 간단한 문법
- 객체 / 함수를 바인딩하기 때문에 순환 참조 관리 편리
- 정적 분석(코드를 실행하지 않더라도 분석이 가능함)이 가능하기 때문에 트리 쉐이킹 쉽게 가능
하지만 비교적 최근 문법이다보니 IE같은 구형 브라우저에서는 제대로 동작하지 않는다는 문제가 있었고 이를 해결하기 위해 나온 아이디어가 바로 트랜스파일러(Transpiler)이다. 즉, 한 번 컴파일하면 구형브라우저에서도 동작하는 자바스크립트 코드가 나오게 만드는 도구인 것이다.
대표적인 트랜스파일러에는 바벨이 있고, CoffeeScript, TypeScript같은 자바스크립트 슈퍼셋 언어의 등장 역시 하나의 방법이 되었다.
태스크 러너
모듈을 만드는 궁극적인 목적은 중복되는 코드를 줄이면서, 생산성과 퍼포먼스가 뛰어난 애플리케이션을 만들기 위함이다.
그러기 위해선 코드 컨벤션을 유지하기 위해 린트를 사용하고, 전처리가 필요한 언어를 컴파일하고, 코드를 축소하고 하나의 파일로 묶는 번들 같은 과정들이 필수적으로 동반된다. 이러한 작업들을 자동화하기 위해 나온 도구가 태스크 러너이다.
대표적으로 Grunt와 Gulp가 있는데 자동화하기 위한 과정 중 하나였던 번들 과정을 좀 더 전문적으로 도와주는 도구가 필요해졌고, 이것이 바로 모듈 번들러의 등장이 된다.
모듈 번들러
모듈 번들러란 자바스크립트 모듈을 브라우저에서 실행할 수 있는 단일 자바스크립트 파일로 번들링하는데 사용되는 프론트엔드 개발 도구이다.
모듈 로더와 모듈 번들러의 차이
- 모듈 로더 : 런타임에 모듈을 가져오기 위한 목적이 더 큼
- 모듈 번들러 : 코드를 프로덕션 환경에서 사용할 수 있도록 준비하는 목적이 더 큼. 또한 빌드 시 모듈을 묶어서 단일 번들 파일로 만들기 때문에 런타임에서 추가적인 로드를 할 필요가 없다.
번들러를 사용하는 이유
- 아직까지 모든 브라우저가 모듈 시스템을 완전하게 지원하지 않음
- 코드의 종속성 관계를 관리하는데 도움
- 종속성 순서, 이미지, CSS 에셋 등을 로드하는데 도움
번들러가 처음 만들어진 이유는 단일 자바스크립트 파일로 번들링 하기 위함이었지만 단순히 번들하는 것을 넘어서서 사용하지 않는 코드를 제거하는 등의 최적화 작업에 대한 필요성도 높아졌다. 이에 구글에서는 2009년 Closure Compiler라는 전문적인 자바스크립트 최적화 도구를 만들게된다.
하지만 최근에는 번들러 자체에서 개발과 빌드, 최적화를 위한 각종 플러그인을 제공하고 있기 때문에 굳이 별도의 태스크러너나 최적화 도구를 쓰지 않아도 되게 되었다. 이러한 변화를 이끈 대표적인 모듈 번들러에는 바로 웹팩(Webpack), 롤업(Rollup), 파셀(Parcel)이 있다.
웹팩 (Webpack)
코드 스플리팅에 있어서는 롤업과 파셀이 성능적으로 뛰어날 수 있지만 아직 안정성 면에서는 웹팩이 더 뛰어나다.
또 다른 웹팩의 특징 중 하나는 웹 애플리케이션에서 사용하는 CSS나 이미지 같은 에셋들을 자바스크립트 코드로 변환하고, 이를 분석해서 번들하는 방식을 사용한다는 점이다. 이 때문에 웹팩의 구성은 다른 번들러에 비해 설정할게 많고 복잡한 편이다.
웹팩에서 제공해주는 개발 서버도 다른 번들러에 비해 뛰어나다. 개발 중 자동 새로고침을 해주는 라이브 리로딩과 새로고침 없이 브라우저의 모듈을 업데이트하는 핫 모듈 교체(HMR, Hot Module Replacement) 기능들이 있다. 또한 기본적으로 해당 옵션이 활성화된 webpack-dev-server플러그인만 설치하면 된다.
하지만 ES6 모듈 형태로 빌드 결과물을 출력할 수 없고, 복잡한 문서가 진입장벽을 높인다는 것이 단점으로 꼽힌다.
롤업 (Rollup)
웹팩과 유사한 번들러이지만, 가장 큰 차이점으로는 ES6 모듈 형식으로 결과물을 출력할 수 있고, 이를 라이브러리나 패키지 개발에 활용할 수 있다는 점이다. 롤업은 ES6 모듈을 기본으로 따르기 때문에 코드 스플리팅 측면에서 다른 번들러와 비교해 강점을 보인다.
다만 파일의 해시 캐스캐이딩(하나의 파일의 해시가 바뀌면 그것을 참조한 파일의 해시도 알아서 바뀜)이 약하다는 점이 약점이다.
파셀 (Parcel)
파셀은 별도의 설정 파일 없이도 동작한다. 즉 설치만 하면 별도의 설정 파일 없이 빌드 명령어를 입력해 바로 사용이 가능하다. 왜냐하면 웹팩과 달리 자바스크립트 엔트리 포인트를 지정해주는 것이 아니라, 애플리케이션 진입을 위한 HTML 파일 자체를 읽기 때문이다.
사용되지 않는 코드를 제거하는 트리 쉐이킹도 ES6 및 CommonJS 모듈 모두에 대해 트리 쉐이킹을 지원하는 강점을 보인다.
또한 파셀은 트랜스파일에 대한 기본 제공을 지원한다. 즉, .barbelrc, .postcssrc, posthtml 같은 설정 파일들을 프로젝트 루트 디렉토리에 만들기만 하면 파셀이 자동으로 파일을 읽어와서 세팅을 해준다.
아직 웹팩이나 롤업에 비해 좁은 생태계, 떨어지는 안정성 등이 단점으로 꼽힌다.\
어떤 번들러를 사용해야할까?
번들러는 기본적으로 프로젝트의 목적에 따라 어떤 번들러를 사용해야할지 다르다. 일반적인 번들러 사용 기준은 다음과 같다.
- 많은 서드파티를 필요로 하는 복잡한 애플리케이션이라면 웹팩
- 최소한의 서드파티로 라이브러리를 만들고 싶다면 롤업
- 복잡한 설정을 피하고 비교적 간단한 애플리케이션을 만들고 싶다면 파셀
구글에서 만든 빌드 도구 간의 장단점을 비교할 수 있는 Tooling.Report라는 사이트를 참고하면 좋을 것 같다.
출처
JavaScript 번들러로 본 조선시대 붕당의 이해 | 요즘IT
지금부터는 모듈과 번들러의 역사에 대한 간략한 설명을 해볼까 합니다. 그렇다고 해서 조선시대 역사까지 알 필요는 없습니다. 저도 그냥 대충 끼워 맞춘 거라서…아무튼 이 글을 통해 JavaScr
yozm.wishket.com