번들러에 대해 공부하다가 정말 엄청난 글을 알게 되었다. 이 글 또한 너무 재밌게 읽어서 공유해 보려고 한다. 일단 읽고 오자
https://wormwlrm.github.io/2020/08/12/History-of-JavaScript-Modules-and-Bundlers.html
이 글을 기반으로 정리를 하려고 한다. 자세한 스토리 흐름은 글에 있으니 간단히 정리해 보겠다.
먼저 모듈 시스템부터.
자바스크립트는 처음에 아주 기본적인 모듈 시스템만 제공했다. 단순히 순서대로 브라우저가 로드하는 방식이었다. 문제는 스크립트를 로드한 전역 콘텍스트에서 각 모듈 간의 충돌이 발생한다는 것이었다. 모듈 간의 스코프가 구분이 되지 않아 다른 파일을 오염시키는 경우가 발생함.
그래서 모듈화에 대한 표준을 위한 움직임이 본격화되었는데 그래서 나온 게 CommonJS와 AMD다. Common JS는 모든 디펜던시가 로컬 디스크에 존재해서 모듈을 바로 사용할 수 있는 환경을 전제로 한다. 그래서 동기적으로 모듈을 호출한다.
AMD(Asynchronous Module Definition)는 CommonJS가 브라우저에게 있어서는 효과적인 방식이 아니었기 때문에 브라우저에서 필요한 모듈들을 네트워크를 통해 비동기적으로 다운로드할 수 있게 하였다. 대표적인 모듈 로더 라이브러리로는 RequireJS가 있다.
위의 두 개는 지향하는 목적이 달랐기 때문에, 이 둘을 통일해서 만든 방식이 UMD(Universal Module Definition) 패턴이다.
하지만 UMD 역시 CommonJS와 AMD의 호환성만을 해결할 뿐이었고, 모듈 시스템의 부재라는 근본적인 문제를 해결하지는 못했다. 그래서 자바스크립트 표준 모듈 시스템이 ECMAScript6 사양에서 명세가 된다. 밑에처럼 우리가 사용하는 방식이다.
import some from "thing";
export default something
이 방식은 동기/비동기 모두 지원하고 CommonJS와는 다르게 실제 객체/함수를 바인딩하기 때문에 순환 참조 관리도 편하다고 한다. 또한 정적 분석(static analyze, 코드를 실행하지 않더라도 분석이 가능함)이 가능하기 때문에, 트리 쉐이킹 역시 쉽게 가능해졌다고 한다. 하지만 구형 브라우저에서는 제대로 동작하지 않는 문제가 있었다. 밑에는 모듈 로더 정의
여기서 모듈 로더(module loader)라는 용어를 한 번 짚고 넘어가고 싶은데요, 모듈 로더는
JavaScript 모듈을 런타임에 로드할 수 있게 만드는 구현체
라고 생각하시면 됩니다. 즉, AMD의 모듈 로더는 RequireJS이고 ES6 방식의 모듈 로더는 네이티브 브라우저가 될 수 있겠죠. SystemJS는 모든 모듈을 로드할 수 있는 만큼 ES6, CommonJS, AMD 방식의 모듈 로더입니다.
그래서 나온 게 Babel 같은 transpiler 들이다. 개발할 때에는 최신 JavaScript 문법을 사용하되, 바벨로 컴파일을 하고 난 후에는 같은 동작을 하면서 구형 브라우저 호환이 되는 JavaScript 코드로 변환되니 생산성 높은 최신 문법을 사용할 수 있게 되었다. 또 다른 방식으로는 TypeScript 같은 슈퍼셋 언어이다. 이런 언어를 사용해 귀찮은 모듈 시스템의 관리는 컴파일러가 대신해주게 된다.
여기까지가 모듈 시스템에 대한 이야기였고 이제부터는 번들러를 위해 좀 더 브라우저 관점, 프론트엔드관점으로 넘어가자.
태스크 러너
번들러 전에 태스크 러너에 대핸 이야기가 나온다. 그 이유는 다음과 같다. 기존에 모듈 시스템을 이야기한 것은 스코프가 구분되는 모듈을 만들기 위함이었다. 애초에 스코프가 구분되는 모듈을 만드는 목적이 중복되는 코드를 줄이고 생산성과 퍼포먼스가 뛰어난 어플리케이션을 만들기 위함이다. 그러기 위해서는 컨벤션을 유지하기 위해 린트를 사용하고, 슈퍼셋언어처럼 전처리가 필요한 언어를 컴파일하고, 코드를 축소하고 파일로 묶는 일련의 과정들이 동반된다. 이러한 과정들의 반복이 특정 작업들을 자동화할 수 있는 도구의 필요성으로 이어졌고 그게 태스크 러너다. 태스크 러너는 프로덕트 개발 과정에서 필요한 일련의 과정들(린팅, 빌딩, 테스팅 등)을 자동화하기 위한 도구이다.
태스크 러너로는 Grunt와 Gulp가 있다. 테스트나 린트, 번들, 최적화 플러그인들을 제공하고 과정을 자동화할 수 있게 해 주었다. 그리고 이 과정에서 번들을 좀 더 전문적으로 도와주는 도구들이 나왔고 그게 바로 모듈 번들러다.
모듈 번들러
모듈 번들러는 JavaScript 모듈을 브라우저에서 실행할 수 있는 단일 JavaScript 파일로 번들링하는데 사용되는 프런트엔드 개발 도구이다. 모듈 로더와 유사하지만, 번들러는 코드를 프로덕션 환경에서 사용할 수 있도록 준비하는것이 목적이다. 또한 모듈 로더는 런타임에 모듈을 가져오는게 목적이지만, 번들러는 빌드 시 모듈을 묶어서 단일 번들 파일로 만들기 때문에 런타임에서 추가적인 로드가 필요 없다.번들러를 사용하는 이유는 크게 세 가지라고 한다. 아직까지 모든 브라우저가 모듈 시스템을 완전하게 지원하지 않고, 코드의 종속성 관계를 관리하는 데 도움이 되며, 종속성 순서, 이미지, CSS 에셋 등을 로드하는 데 도움이 되기 때문이다.번들러는 이제 단순히 단일 파일로 번들링 하는 것을 넘어서서 번들러 자체에서 개발과 빌드, 최적화를 위한 각종 플러그인들 제공하고 있어서 별도의 태스크 러너나 최적화 도구가 필요 없다고 한다.그리고 이런 번들러의 대표 주자가 웹팩(Webpack), 롤업(Rollup), 그리고 파셀(Parcel)이다. 마지막으로 3개를 정리하면서 글을 마치겠다.
웹팩
웹 애플리케이션에서 사용하는 CSS나 이미지 에셋들을 자바스크립트 코드로 변환하고 분석해서 번들하는 방식을 사용 그래서 설정할게 많다. 또한 코드 스플리팅에 있어서는 웹팩 보다 나머지 두 개가 더 뛰어나다. 그렇지만 웹팩은 안정적이고 개발 중 변경사항을 자동으로 새로고침 해주는 라이브 리로딩(Live reloading) 기능과, 새로고침 없이 런타임에 브라우저의 모듈을 업데이트하는 핫 모듈 교체(HMR, Hot Module Replacement) 기능 등을 지원한다. 트리 쉐이킹이 지원이 되지만 CommonJS방식으로 사용한 부분을 ES6으로 교체해야 한다. 그리고 ES6 모듈 형태로 빌드 결과물을 출력할 수 없다.
Rollup
롤업은 웹팩과 유사한 모듈 번들러이지만, 가장 큰 차이점을 꼽으라면 ES6 모듈 형식으로 빌드 결과물을 출력할 수 있으므로 이를 라이브러리나 패키지 개발에 활용할 수 있다는 것이다. 웹팩과 파셀은 자체 로더가 있지만 롤업은 ES6 모듈을 기본으로 따르기 때문이라고 한다. 그래서 코드 스플리팅도 잘하는데, 중복 제거에 특화되어있다. 진입점이 여러 개 있을 때 중복해서 번들 될 수 있는 부분을 알아내고 독립된 모듈로 분리해 낸다.
Parcel
Parcel은 별도의 설정 파일이 필요 없고, HTML 파일 자체를 읽어서 엔트리 포인트 지정해줄 필요가 없다. 트리 쉐이킹도 잘한다. 일반적으로 번들러들은 원래 자바스크립트만 읽어서 css나 이미지 에셋들의 종속성을 인식하고 추가하려면 트랜스파일러를 사용해야 함. 파셀은 트랜스파일이 필요한 파일 유형을 따로 설정할 필요가 없이 파일만 추가해주면 자동으로 세팅해준다.
더욱 자세한 내용은 위의 소개해준 블로그 글을 읽으면 될 거 같다. 번들러의 구체적인 설정 부분은 따로 또 정리를 해야 될 거 같다. 위의 글을 거의 줄여 쓴 거나 다름없어서 정리는 편했다. 결국 모듈 시스템의 발전 과정을 통해 애플리케이션 개발의 생산성이 어떻게 향상되었는지 알게 된 거 같다.
'TIL' 카테고리의 다른 글
JS. Closure (0) | 2022.01.17 |
---|---|
Redux, Redux-toolkit은? (0) | 2022.01.16 |
JS. 코드 스플리팅 그리고 트리 쉐이킹 (0) | 2022.01.14 |
JS. 프로토타입 (0) | 2022.01.14 |
JS. 자바스크립트 실행컨텍스트 (0) | 2022.01.13 |