번들러 파헤치기 2 - 번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, esbuild, vite, turbopack)
Code/JavaScript

번들러 파헤치기 2 - 번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, esbuild, vite, turbopack)

반응형

Photo by Randy Fath on Unsplash

본 글은 번들러 파헤치기 시리즈의 2부로, 번들러의 발전 및 역사를 주제로 각 번들러에 대한 설명을 진행하려고 합니다.

2부에서도 CommonJS(cjs), esmodule, ESM (mjs)에 대한 내용이 등장하기 때문에 이전 포스팅을 가볍게 읽고 오시는 것을 추천드립니다.


번들러 파헤치기

1부 - 모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-esmodule)

2부 - 번들러의 발전과 역사 (HTTP/1.1, webpack, rollup, parcel, snowpack, exbuild, vite, turbopack)

3부 - 오픈소스 라이브러리 만들기 (rollup / react / typescript / babel)


2부 번들러의 발전과 역사

자바스크립트에서의 빌드 환경은 모듈 시스템 배경과 밀접한 관련이 있습니다. 

1부에서 서술한 대로, ESCAMScript2015에 자바스크립트 모듈 표준인 esmodule이 등장하기 이전까지는 자바스크립트 모듈 표준이 존재하지 않았습니다.

이러한 문제를 해결하고자 모듈화를 위한 다양한 움직임들이 있었고, CommonJS나 AMD, UMD 등 Module Definition 명세를 토대로 다양한 라이브러리들과 esmodule이 탄생, 등장했습니다.

 

1. 번들러의 탄생

웹 생태계의 발전으로, 웹 애플리케이션의 규모도 점점 커져나가며 다루어야 하는 파일들이 많아지기 시작했습니다.

브라우저는 필요한 리소스를 네트워크를 통해 받아옵니다.
필요한 파일들이 많아지면 많은 파일을 네트워크로 요청할 수밖에 없었고, 그로 인한 응답 지연, 동시 전송 문제 등이 사용자 경험에 영향을 끼쳤습니다.

또한 파일에 실행 우선순위가 존재함에도 이 실행 순서를 제어할 수 없었기 때문에 원치 않는 동작이 발생할 수 있었습니다.

문학 작품을 해석하려면 작품이 만들어진 시기의 시대상을 이해하는 게 중요하다고 하죠, 개발도 똑같습니다.

이 당시 HTTP 표준 스펙은 HTTP/1.1로, 무거운 헤더와 HOLB (Head-Of-Line Blocking)라는 문제를 가지고 있었습니다.

HOLB는 다중 요청은 가능하나, 먼저 들어온 요청에 대한 응답이 끝나야 다음에 들어온 요청에 대한 응답을 받을 수 있는 것인데, 앞에 있는 요청 Head에 문제가 생길 경우 뒤에 있는 요청이 블로킹되어있는 것을 뜻합니다.

또한 무거운 헤더를 가지고 있었기에 네트워크 요청을 최소화 해야만 했을 것입니다.

(2015년 스트림을 통해 데이터 교환을 진행하는 HTTP/2가 표준으로 채택되며 HOLB문제는 해결되었으나, HTTP/2 표준의 기반이 되었던 SPDY 프로토콜의 경우에도 2009년도에 나왔기 때문에 이 당시는 지금보다 네트워크 요청 최적화의 중요성이 더욱더 높았을 거라고 생각합니다.)

 

'파일을 하나로 합치면 되지 않을까?'

그렇게 CSS, 폰트, 이미지, JS를 정적인 파일로 변환하고 하나로 합쳐주는 '번들러'가 탄생하게 됩니다.

들어가기에 앞서 각 번들러 관련 주요 용어에 대해 설명하고 넘어가겠습니다.


용어 정리

Tree-Shaking (트리 쉐이킹)

  • 사용하지 않는 불필요한 코드를 최종 빌드 결과물에서 제거하여 용량을 최적화하는 기법

 

HMR (Hot Module Replacement)

  • 코드의 수정이 발생할 경우 새로고침 없이도 바로 변경된 부분을 확인할 수 있는 개발 효율성을 크게 향상시키는 중요한 기능
    • 코드의 수정이 발생하면 런타임에 이를 알리고 강제로 새로고침을 진행하는 Hot-Reloading 개념을 모듈에 적용시켜 확장한 개념으로 볼 수 있습니다.

 

code-spliting (코드 스플리팅, 코드 분할, 번들 분할)

  • 번들을 모두 하나로 통합하는 것이 아닌, lazy-loading을 통해 필요한 시점에 분리된 번들을 요청하는 식으로 번들을 나누어 개별 번들 사이즈를 줄이는 방식

2. 번들러의 특징

개인적으로 번들러 공부를 하면서 가장 어려웠던 게 각 도구들의 의존 관계와 역할을 파악하는 것이었습니다.

번들러의 특징을 검색하면 공통적으로 나오는 내용이 개발 서버 HMR을 제공하고, 정적 파일을 모두 하나로 합칠 수 있으며 (지정한 포맷으로), 이외 다른 빌드 기능도 제공한다는 내용이 존재합니다.

사실 자세히 말하자면 HMR을 제공하지 않는 번들러도 존재하고, 어떤 걸 보고 누군 번들러라고 하고 누군 번들러가 아니라 프론트엔드 빌드 도구라고, 또 누군 컴파일러라고 하기도 하고, 어떤 번들러는 다른 번들러를 또 번들러로 사용하기도 하고 대혼돈을 겪었던 것 같습니다.

그렇기에 번들러의 공통 특징을 떠올리기 보단 각 도구들이 어떤 부분에 초점을 두고 어떤 문제를 해결하고자 했는지에 초점을 두고 글을 읽어주셨으면 합니다. 

이 글에서는 도구 개념의 정의를 공식문서의 소개 부분을 그대로 인용하여 설명하겠습니다.

 

3. 번들러의 발전

 

빌드 도구의 역사

 

3-1. Webapck, 모듈 번들러의 등장 - 2014

webpack is a static module bundler for modern JavaScript applications - webpack core concepts

단순한 파일 통합을 넘어서 개발 편의 기능을 제공하는 정적 모듈 번들러, Webpack이 탄생했습니다.

웹팩은 CSS, 폰트, 이미지, JS를 하나의 파일로 압축하여 관리함으로써 네트워크 요청 수를 줄이고, Tree-Shaking 기법을 통한 빌드 결과물 최적화, HMR (Hot Module Replacement), code-spliting 기능을 제공합니다.

웹팩은 개발 서버 기능인 webpack-dev-server가 존재합니다.

`webpack-dev-server`와 HMR (Hot Module Replacement)은 밀접한 관계가 있습니다.
`webpack-dev-server`는 개발 중인 웹 애플리케이션을 위한 HTTP 서버를 제공하며, HMR 기능을 통해 코드 변경 시 페이지 새로고침 없이 변경된 모듈을 실시간으로 교체할 수 있게 합니다. 이를 통해 개발자는 빠르게 변경 사항을 보고, 애플리케이션의 상태를 유지하면서 개발할 수 있습니다. 

Webpack은 인터프리터 언어인 자바스크립트로 구현되어 있으며, 규모가 크고 복잡한 애플리케이션을 관리하는 것에 중점을 두었습니다.

그렇다 보니 초기 설정이 복잡하여 러닝 커브가 높은 편입니다.
많은 플러그인과 로더를 구성해야 하며, 큰 프로젝트에서는 구성 파일이 매우 방대해질 수 있습니다.
그로인해 빌드 시간이 길어질 수 있으며, 특히 프로젝트가 커질수록 이 문제는 더욱 심화됩니다.

React 프로젝트를 생성할 때 사용하던 CRA(create-react-app)의 경우에도 webpack 번들러가 적용되어 있습니다.
React 프로젝트를 실행시키기 위해 필요한 복잡한 번들러 설정을 미리 템플릿으로 만들어 두고 CLI를 통해 만들어내는 것이죠.

react-scripts eject

( CRA 프로젝트에서 script에 존재하는 'eject'를 실행시키거나, 위 CLI명령어를 입력하게 되면 숨겨져 있던 파일들이 모두 노출되며 그곳에서 웹팩의 자세한 설정을 확인할 수 있습니다. - webpack.config.js )

한 번 실행하고 나면 다시 돌려 놓는 것이 불가능하기 때문에 주의해서 사용해야 합니다.

(궁금해서 브랜치를 따로 만들어서 실행해 본 적이 있는데 굉장히 많은 파일과 복잡한 설정들을 보고 놀랐던 기억이 납니다)

Webpack은 이처럼 단점도 존재하지만, 번들러의 개념을 처음 탄생시킨 도구로서 최적화, 강력한 모듈 번들링 기능과 함께 광범위한 생태계를 제공하며, 많은 개발자들에게 선택되었습니다.

초기 웹팩이 릴리즈 되는 시점은 esmodule 표준이 채택되기 전으로 commonjs인 cjs 포맷만을 지원했다는 특징이 있습니다.

(2020.10 릴리즈 된 v5에 esmodule 포맷 지원이 추가되었습니다.)

 


 

3-2. Rollup.JS, ESM (esmodule) 지원 번들러의 등장 - 2015

The Javascript module bundler - Rollup.JS

Rollup은 경량화와 번들 최적화를 중점에 둔 esm 지원 모듈 번들러입니다.

ECMAScript2015에 자바스크립트 내부 스펙으로 esmodule이 추가되었습니다.
(모듈 시스템 관련 이전 포스팅 참고)

기존 cjs와 esmodule의 가장 큰 차이점이라면 모듈의 정의, 로딩 방식을 꼽을 수 있습니다.

commonJS (cjs) 

/* test.js */
module.exports = 'hello'

/* index.js */
const hello = require('./test.js')

esmodule (mjs)

/* test.js */
const hello = 'hello'
export default hello;

/* index.js */
import hello from './test.js'

cjs의 경우 module.exports 객체를 통해 모듈을 정의하고, require 함수를 통해 모듈 로더가 동기적으로 불러옵니다.
esmodule의 경우 import, export 구문을 통해 모듈을 정의하고, import를 통해 비동기적으로 모듈을 불러옵니다.

module.exports 구문은 객체를 통해 관리됩니다. 객체가 갖는 특성이 있기 때문에 모듈 내에서 동적으로 module.exports를 변경하며 할당할 수 있습니다. 그렇기에 모듈을 실행한 뒤에야 반환 값을 알 수 있으며, 모듈 간 의존성을 파악하는 게 구조적으로 어렵다는 문제가 있습니다.

반면, esm은 import, export 구문을 통해 내보낼 모듈, 불러올 모듈을 명확히 파악할 수 있다 보니 모듈 간 의존성 파악을 명확하게 할 수 있다는 특징을 갖고 있습니다.

Rollup은 공식문서에서도 강력한 Tree-shaking을 제공한다고 소개하고 있습니다.

웹팩 이후에 등장한 모듈 번들러로서 당시 웹팩에는 없던 esm을 지원하며 의존성 파악이 명확하여 사용하지 않는 코드를 제거하는 Tree-shaking을 더 강력하게 지원하게 되었다고 볼 수 있습니다.

(esm이 아닌 cjs포맷으로도 번들링 가능하나, esm 최적화에 특화된 도구)

롤업은 다양한 번들 포맷과 Tree-shaking, code-spliting을 제공합니다.
자체적으로 HMR 기능이 구현되어 있지는 않지만, 플러그인 rollup-plugin-hot을 통해 HMR을 사용할 수 있습니다.

롤업은 번들 사이즈 경량화와 최적화에 중점을 둔 번들러로서, 라이브러리를 구현하는 데에 많이 쓰입니다.

(라이브러리는 기존 패키지에 설치되는 모듈로서, 패키지의 빌드 결과물에 함께 포함되기 때문에 더더욱 경량화에 신경 써야 하기 때문)

또한 현재 제일 각광받고 있는 프론트엔드 빌드 도구인 Vite 번들러의 내장 번들러로서도 사용되고 있습니다.

 


 

3-3. Parcel, zero-configuration 번들러의 등장 - 2017

The zero configuration tool - Parcel

Parcel은 webpack과 Rollup와 같은 복잡한 설정 없이 바로 사용할 수 있는 번들러를 목표로 탄생했습니다.

zero configuration의 특징을 갖고 있다 보니 러닝커브 또한 낮으며, Parcel은 멀티 코어 처리를 활용하여 빠른 빌드 시간을 제공하고 파일 시스템 캐시를 사용하여 재빌드 시간을 단축시킬 수 있으며, HMR 기능을 제공한다는 특징이 있습니다.

Parcel 공식 문서

이렇듯 Parcel은 사용법이 간단하고, 빠른 성능을 가지고 있으나 단점 또한 존재합니다.

zero-configuration 방식은 초기 설정이 쉽지만, 좀 더 복잡한 프로젝트에서 필요한 세밀한 최적화나 커스터마이징에 한계가 존재하며, 프로젝트의 규모가 커질수록 파일 수와 의존성이 급격히 증가하여 캐시 관리와 멀티 코어 리소스를 효율적으로 사용하는 데에 어려움이 발생할 수 있습니다.

다른 번들러에 비해 사용자 커뮤니티가 좁은 편에 속하며, 때문에 세밀한 최적화와 다양한 설정이 필요한 큰 프로젝트보다는 중소 프로젝트에 적합한 번들러라고 볼 수 있습니다.

 


 

3-4. esbuild, 100배 빠른 번들러의 등장 - 2020

An extremely fast bundler for the web - esbuild doc

esbuild 번들러는 빌드 도구 성능에 새로운 시대를 열고, 사용하기 쉬운 번들러를 만들기 위해 탄생했습니다.

- The main goal of the esbuild bundler project is to bring about a new era of build toll performance, and create an easy-to-use modern bundler along the way

 

esbuild의 주요 기능은 아래와 같습니다.

  • 캐싱이 필요 없는 최고의 속도
  • JavaScript, CSS, TypeScript JSX 내장
  • ESM과 CommonJS 모듈 번들 지원
  • CSS모듈을 포함한 CSS번들 생성
  • Tree shaking, 번들 minification, source map 지원
  • code spliting (예정)

 

번들러를 학습하며 파악하는 데에 가장 오랜 시간이 걸렸던 번들러입니다. 그래서 가장 흥미로웠던 번들러인데요, '새로운 시대를 연다'라는 설명에 알맞게 기존의 번들러와 약간은 다른 특징을 띄고 있었기 때문입니다. 그러다 공식 문서를 통해 해답을 찾을 수 있었습니다.

esbuild의 제작자 Evan Wallace공식 문서 FAQ를 통해 esbuild가 프론트엔드의 올인원 도구가 되어야 한다고 생각하지 않는다고 기술하였습니다.

I want to avoid the pain and problems of the "webpack config" model where the underlying tool is too flexible and usability suffers

너무나 유연하기에 오히려 유용성이 저하되는 webpack의 구성을 예로 들며 웹팩과 같은 모델을 지양하고 있음을 알 수 있습니다.

지원할 예정이 없는 기능들

 

esbuild는 그럼 왜 이렇게 빠른 것인가?

비교군을 웹팩으로 두고 설명해 보겠습니다.

 

인터프리터 vs 컴파일

우선 웹팩은 인터프리터 언어인 javascript로 구현되었습니다.
반면 esbuild는 컴파일 언어인 Go로 구현되었습니다.

사람이 작성한 프로그래밍 언어가 실제로 실행이 되기 위해선 컴퓨터가 알아들을 수 있는 기계어로 변환되어야 합니다.
이 방식으로 인터프리터, 컴파일, JIT 컴파일이 존재합니다. 설명을 위해 인터프리터 언어와 컴파일 언어만 비교군으로 두겠습니다.

인터프리터 언어

  • '통역사', 한 줄 한 줄씩 컴퓨터 언어로 바꾸며 바로 실행
  • 초기 구동시간이 짧다
  • 전체 명력 속도는 컴파일 언어에 비해 느리다

컴파일 언어

  • '번역서', 소스코드를 한 번에 다른 목적 코드로 변환한 후 한 번에 실행
  • 컴파일 과정 : 고급언어 -> 변환 -> 로우 레벨 언어 -> 실행 파일 생성 및 실행
  • 규모가 큰 프로젝트의 경우 빠르게 동작
  • 컴파일 시 시간 소요
  • 메모리 차지

정보를 찾아보다가 이해가 빠르게 되는 문장을 보아 인용하자면, (인터프리터) 동시통역 vs (컴파일) 번역서

동시통역을 하는 것보다 번역서를 읽는 것이 훨씬 빠를 것입니다. 

또한 자바스크립트는 싱글 스레드로 작동하기에 한 번에 한 가지 일만 처리할 수 있어 처리 속도가 제한적인 반면, Go 언어는 core자체가 병렬 처리를 위해 설계되었기 때문에 필요한 스레드를 만들고 여러 작업을 동시에 처리할 수 있습니다.

Go언어로 구현된 esbuild는 CPU를 최대한 많이 사용하는 방향으로 설계되었으며, CPU캐시를 적극 사용토록 하여 JS를 파싱해 만드는 구문 분석 트리 AST를 캐시에 최대한 오래 머물도록 하여 속도를 높였습니다.

단점이라면 아직 1.0.0 버전에 도달하지 못했다는 것, es5를 완벽히 지원하지 않는다는 것을 이야기할 수 있을 것 같습니다.

 

버전

여전히 esbuild는 활발하게 개발되고 있으며, Vite의 pre-bundling의 도구로 사용되어 빠른 개발 서버를 제공하고, snowpack v3.0.0에서도 번들링 도구로 채택되었지만 현재 날짜 기준 (2024.02.26) 가장 최신 버전은 v0.20.1입니다.

제작자는 esbuild가 안정된 상태에 들어왔다고 설명하였으나, 아직 v1.0.0 미만 버전이라는 점에서 다수의 개발자들에게 충분히 고민될 요소라는 생각이 듭니다.

 

ES5 지원

또한 esbuild는 es5를 지원하지 않는 것은 아니지만, 100% 지원은 하지 않고 있습니다.
IE가 서비스를 종료하긴 했지만, 모든 개발자들이 es5지원을 포기할 수는 없는 노릇입니다.

이에 대한 내용을 찾다가 깃허브에서 2020년에 작성된 이슈를 발견했습니다.

활발한 논의들과 es5로 변환하기 위한 대체 방안이 남겨져있습니다.

2023년 제작자 Evan Wallace는 es5문제를 해결하기 위한 다른 방안들이 존재하며 (SWC), 작업의 우선순위 상 소수의 사용자를 위한 es5에 대한 지원 작업은 합리적이지 않다고 생각한다는 의견을 남기며 이슈를 닫았습니다.

역시 기가 막힌 것을 만들어내기 위해선 우선순위 선정 능력과 약간의 카리스마가 필요하다고 봅니다.

 

Rollup.js : rollup-plugin-esbuild 

Webpack : esbuild-loader

esbuild의 빠른 번들링과 축소화를 rollup과 webpack 번들러에서도 플러그인과 로더를 통해 사용할 수 있습니다.

 


 

3-5.  Snowpack, 빠른 웹 개발을 위한 프론트엔드 빌드 툴의 등장, - 2019

Snowpack is a lightning-fast frontend build tool - snowpack doc

snowpack은 ESM을 활용하고 개발 서버에 중점을 둔 빌드 툴로, 번들링 하지 않는 개발(Unbundled Development)을 핵심 개념으로 갖고 있습니다.

 

Unbundled Development?

snowpack - How Snowpack Works

스노우팩은 빠른 개발 서버를 구축하는 데에 중점을 두었고, 웹팩과 비교하여 설명하였습니다.

웹팩 롤업과 같은 기존 번들러들은 코드의 수정이 발생하면 변경된 파일을 다시 빌드하고, 전체 파일에 대한 번들링을 진행합니다.
이 경우 O(n)의 복잡도를 갖게 됩니다.

스노우팩은 ESM이 널리 사용되며 파일 간 의존성 파악이 명확해졌기에 이는 더욱 불필요한 과정이라고 생각했고, 번들링은 필요한 때에 선택적으로 진행할 수 있어야 한다라는 컨셉 하에 코드의 변경이 발생할 경우 해당 파일만 리빌드하고 캐시로 갖고 있도록 구현하였습니다.

(번들링을 아예 하지 말아야 한다는 것이 아니라, 개발할 땐 번들링이 불필요하다는 주장입니다. 실제로 스노우팩은 웹팩, 롤업, Parcel과 같은 번들러를 플러그인으로 지원하였습니다)

Single-file 빌드는 빠르고, 디버깅이 쉬우며, 프로젝트의 사이즈가 개발에 영향을 미치지 않으며, 캐시가 간단하다는 특징이 있습니다.

이를 통해 50ms 내 개발 서버 구동을 진행할 수 있게 되었습니다.

또한, 내장 빌드 도구로는 번들러인 esbuild를 사용했습니다.
esbuild는 빠른 빌드가 가능했으나 모든 기능을 하나로 통합하는 올인원 번들러가 아니었습니다.

스노우팩은 실제 프로덕션을 위한 빌드 결과물은 웹팩, 롤업과 같은 번들러를 사용할 수 있도록 하고, 개발 중일 때에는 빠른 빌드가 가능한 esbuild를 채택하여 개발 경험을 개선했습니다.

설명을 위해 snowpack보다 esbuild에 대한 설명을 먼저 진행했었는데, 사실 시기적으로는 snowpack이 esbuild보다 먼저 출시되었습니다.


먼저 나왔는데 어떻게 esbuild를 사용할 수 있었던 것일까요?

2021.01.13 작성된 v3.0.0 릴리즈 노트를 통해 그 내용을 알 수 있었습니다.

Snowpack already uses esbuild internally as our default single-file builder for JavaScript, TypeScript and JSX files. Snowpack v3.0 takes this integration one step further, with a new built-in build optimization pipeline.

스노우팩은 이미 JSX, js, ts파일의 싱글 파일 빌더로 esbuild를 사용 중이었고, v3.0에 추가적으로 esbuild를 사용한 내장 빌드 최적화 파이프라인을 구축하였다고 설명하였습니다.

이전 릴리즈 노트에선 esbuild와 관련된 내용을 찾을 수 없었지만, 이를 통해 최초 릴리즈 이후 esbuild를 채택했다고 추측할 수 있습니다.

스노우팩은 2020 자바스크립트 오픈 소스 어워즈에서 개발 생산 부스터로 1위를 차지했었으나, 현재 사실상 지원 종료를 알리며 Vite을 대안으로 추천하고 있습니다.

 


 

3-6. Vite, 빛처럼 빠른 프론트엔드 빌드 툴 등장 - 2020

Next Generation frontend build tooling - Vite doc

Vite은 ESM을 이용한 개발서버와 Rollup 최적화 빌드 커맨드를 제공하는 프론트엔드 빌드 툴입니다.

자바스크립트 어플리케이션은 점점 더 많은 기능을 필요로 했고, 그로 인해 필요한 모듈의 수도 점점 늘어갔습니다.

번들링 도구들이 다수 등장했으나 다루어야 하는 자바스크립트 모듈이 많아질수록 개발서버의 구동 시간과 코드의 수정이 브라우저에 반영되는 시간이 점점 느려지는 문제가 있었고, Vite은 이를 해결하고자 느린 개발서버와, 느린 업데이트 문제를 해결하고자 했습니다.

우리는 의존 관계를 명확히 파악할 수 있도록 해주는 ESM의 특징, 빠른 개발 서버 구동을 위해 Unbundled Development에 초점을 맞춘 Snowpack의 배경을 보고 왔습니다.

Snowpack is also a no-bundle native ESM dev server that is very similar in scope to Vite.

Vite은 번들하지 않는 ESM개발 서버를 제공했던 snowpack과 코어 컨셉이 일치하고, 많은 영역에 대해 유사함을 가지고 있습니다.

 

느린 개발 서버

느린 개발 서버 문제를 해결하기 위해 Vite은 소스 코드를 두 가지 영역으로 나누어 처리하도록 구현하였습니다.

의존성 ( Dependencies )

패키지의 디펜던시들은 개발 도중 변경이 일어나는 부분은 아닙니다. 이를 Plain javascript라고 볼 수 있으며, 이런 의존성 모듈을 효과적으로 관리하기 위해 Dependency pre-bundling 개념을 적용했습니다.

Vite은 esbuild를 통해 의존성을 단일 모듈로 번들링합니다.

의존성이 필요로 하는 모듈에 대한 HTTP요청을 단 한 개의 요청으로 사용할 수 있도록 하나로 합친 뒤, 모든 의존성을 캐시상태로 /node_modules/.vite/ 경로에 저장하고 캐싱된 모듈을 사용할 수 있도록 합니다.

 

소스 코드 ( Source code )

소스코드는 ESM을 통해 제공하고, 라우팅 베이스 import를 사용하여 Vite은 브라우저가 요청하는 대로 코드 변환 후 해당 페이지에 필요한 유효 범위를 갖는 모듈들을 제공할 수 있도록 구현했습니다. 

이렇게 코드의 수정이 발생하면 의존성을 포함한 모든 모듈을 다시 번들링 하여 재구성하여 발생하던 Bundle based bundler 문제를 해결했습니다.

 

느린 업데이트

느린 업데이트의 문제는 HMR을 번들러가 아닌 ESM을 통해 진행할 수 있도록 HTTP 헤더를 적극 활용하여 해결했습니다.

Vite은 파일의 수정이 일어날 경우 변경된 부분만 수정하여 파일을 교체하고, 브라우저 요청이 발생하면 교체된 파일을 보내주는 역할만 진행함으로써 ESM과 캐싱을 적극 활용했다고 볼 수 있습니다.

(캐싱 관련 내용은 HTTP cache 파헤치기 포스팅에서 확인 가능합니다)

Vite은 제공되는 chunk 파일의 모듈은 <link rel="modulepreload"/>를 통해 html에 주입하여 브라우저가 javascript 모듈을 미리 로드하고 캐시 할 수 있도록 하여 모듈을 병렬적으로 불러올 수 있도록 하였으며, 또 캐싱을 적극 활용하여 성능 최적화에 많은 노력을 기울였음을 알 수 있습니다.

그리고 Vite은 pre-bundling은 esbuild를, production을 위한 build 번들러로는 Rollup을 채택했습니다.

 

esbuild도 번들 기능이 있는데 왜 Rollup을 사용했는가?

esbuild는 롤업과 비교했을 때, 아직 플러그인의 유연성이 부족한 상태입니다.

esbuild는 rollup보다 빠르지만, 메인 번들러로 사용하기에는 플러그인 지원이 부족하여, Vite은 Rollup을 번들러로 구성하여 자체 빌드 커맨드와 CLI를 통해 유연성을 확보하고자 하였고, 개발 서버는 esbuild를 통해 빠르게, 빌드는 Rollup을 사용하여 유연성 있게 설계를 진행했습니다.

 

왜 Snowpack보다 Vite였나?

이 의문은 공식 문서를 통해 확인할 수 있었습니다.

Snowpack은 production을 위한 번들링을 다른 번들러를 직접 선택하여 진행할 수 있었습니다. (webpack, rollup, parcel) 번들러에 따라 최종 결과물의 출력이 다르기 때문에 Vite은 이 빌드 과정을 더 밀도 높은 통합을 통해 간소화된 경험을 제공하기 위해 Rollup을 단일 번들러로 구성했습니다.

이로 인해 MPA (multi-page app), 자동 css spliting, 최적화된 비동기 청크 로딩, auto polifill import 지원을 통해 snowpack 보다 넓은 범위의 기능을 지원할 수 있게 되었습니다.

Vite 점유율이 궁금해서 찾아보다가 재밌는 리서치를 발견했습니다.

참고 : https://2022.stateofjs.com/en-US/libraries/build-tools/

Vite은 Javascript 빌드 도구 중, Retention 수치 ( would use again / (would use again + would not use again) ) 98%로 1등을 차지했습니다.

Usage 항목((would use again + would not use again) / total)을 보니 첫 모듈 번들러로서 가장 오랜 기간 자리를 지켜 온 webpack이 85%로 1등을, Vite이 49%로 3위 차지하고 있는 걸 확인할 수 있었습니다.

2022년 자료로, 현재 2023년 설문이 진행 중인데 1년 사이 Vite의 Usage가 많이 오르지 않았을까 합니다.

저 또한 담당하던 CRA 프로젝트가 점점 거대해지며, 코드 스플리팅도 적용하고 번들 최적화를 수시로 진행했었는데도 모듈이 하도 커져서 개발 서버를 한 번 키는 것부터 상당한 시간이 걸렸던 적이 있습니다.

HMR 반영이 느려져 강제로 새로고침하며 사용하던 게 습관이었는데 그것마저 CSR 환경이다 보니 날이 갈수록 개발 경험이 안 좋아지던 문제가 있어 Vite으로 마이그레이션을 진행한 적이 있습니다.

마이그레이션이 어렵지 않아 빠르게 수정하여 배포했고, esm파일을 통한 빠른 디버깅 + 갑자기 비약적으로 상승한 개발 서버의 속도 덕에 동료와 함께 '와 대박이다' 했던 기억이 납니다. (매트로 잘 가!!)

(이후로 CRA 대신 무조건 create-vite을 쓰고 있습니다)

Vite은 여전히 활발히 개발이 이루어지고 있고, 개인적으로 정말 차세대 프론트엔드 빌드 툴이라는 타이틀이 잘 어울리는 도구라고 생각합니다.


 

3-7. Turbopack, Vite보다 5배 빠른 Rust 기반 Webpack 후속 번들러 등장 - 2022

Turbopack is an incremental bundler optimized for Javascript and Typescript, written in Rust. - Turbopack doc

터보팩은 Next.js, SWC 등을 만든 Vercel에서 제작한 공식 webpack 후속 번들러입니다.

터보팩은 Next.js 13 버전 번들러로 추가되었고, 공식 문서를 통해 기존의 번들러로 SSR 기능을 사용하는 것은 각 런타임 환경에 대해 컴파일러를 생성하고 번들을 연결하여 통신을 관리하는 것에 대한 유지 보수 비용의 부담이 존재했고, 이를 해결하고자 자체 번들 시스템을 구현하였다고 설명했습니다.

Turbopack은 함수 수준의 캐싱을 제공하는 터보 엔진을 구현했습니다.

증분 계산을 가능케 하는 Rust의 turbo 라이브러리 기반으로 동작하며, 호출된 내용과반환된 내용을 메모리 내 캐시에 저장하여 증분 업데이트 계산 속도를 올렸다고 주장합니다.

또한 터보팩은 요청한 부분에 대한 컴파일만 최소한으로 진행하여, lazy bundling을 설명했습니다.
esm은 필요한 모듈을 전부 받아와야 하기 때문에 필요한 모듈이 많아질수록 네트워크 요청 수가 많아지는 부담이 있었고, esbuild의 경우 lazy loading 기능이 존재하지 않아 Turbopack은 수신된 요청을 기반으로 모듈 그래프를 작성하여 최소한으로 필요한 코드만을 번들로 묶어 제공하도록 구현되었습니다.

 

이를 통해 Turbo 엔진은 최대 속도에서 최소한의 작업만 수행하는 것을 목표로 하는 것을 알 수 있습니다.

터보팩은 초기에 공식 문서의 밴치마크를 통해 Vite보다 10배 빠르고, webpack보다 700배 빠른 번들러임을 주장했습니다.
궁금해서 정말 10배나 빠른 건지 찾아보았습니다.

Vite 제작자 에반 유는 직접 Turbopack이 정말로 Vite보다 10배 빠른지 벤치마킹을 진행하였고 Turbopack이 주장한 내용과 일치하지 않는 부분이 있다며 벤치마크 정정 요청을 진행했던 것을 확인할 수 있었습니다.

Is Turbopack really 10x Faster than Vite? - Evan you

Turbopack은 초기 벤치마킹 환경에 대한 상세한 설명이 존재하지 않았고, 이후 정정한 벤츠마크 내용을 Turbopack 블로그에 작성했습니다.

We are now measuring Turbopack to be consistently 5x faster than Vite for HMR, over all application sizes.

Turbopack은 HMR 환경에서 Vite보다 5배 빠른 것으로 내용을 정정했습니다.

 

또한 에반유는 이 성능 차이에 대해 위 벤츠마크 레포 이슈 스레드에 아래와 같은 의견을 남겼습니다.

Vercel의 벤치마크(다시 말하지만 Vite는 SWC를 사용하지 않음)의 수치를 보면 최대 10,000개 모듈까지 ~15ms 대 ~100ms 범위에 있습니다. 숫자는 20,000 범위 위에서만 기울어지기 시작합니다. Vite의 경우 종속성은 사전 번들로 제공되므로 사용자 소스 모듈만 HMR을 설명하며 앱이 그렇게 클 가능성은 거의 없습니다.

 

그렇다면 Vite이 아닌 Turbopack을 쓰는 게 좋지 않나?

Turbopack은 로드맵을 통해 SvelteKit와의 밀도 높은 통합, 다른 프레임워크의 지원, 웹팩 사용자를 위한 마이그레이션에 대한 계획을 가지고 있음을 확인할 수 있습니다.

다만 Turbopack은 2024.03 현재 베타 상태로, Next.js 개발서버에서 선택 기능으로 사용되고 있습니다.

Turbopack만이 갖는 이점이 존재하고는 있으나, 아직 베타 버전이며 Vite과 비교했을 때 플러그인 생태계와 안정성 부분에 대한 차이 또한 명확히 존재하는 것이 사실이기에 production에 사용하기 위한 번들러로는 아직 고려해야할 것이 많은 도구라는 생각이 듭니다.

 


 

이번 포스팅은 번들러의 발전과 역사에 대해 작성해보았습니다.

이 글은 특정 도구를 당장 사용하기 위함이 아닌, 도구들이 당시 어떠한 문제를 해결하고자 했는지 기술의 발전에 맞춰 설명하기 위해 작성되었습니다.

그 어떤 도구도 장, 단점은 존재합니다.

지금이야 웹팩보단 다른 번들러를 선호하지만 그럼에도 불구하고 웹팩의 점유율이 왜 높은 건지, rollup은 왜 나왔는지, esbuild는 왜 기존 번들러와 다른 개념을 추구하는지, 왜 도구들이 esbuild를 같이 사용하는지

이러한 것들은 그 당시 각 도구들이 해결하고자 했던 문제를 떠올리면 실마리를 찾을 수 있는 것 같습니다.

다음 포스팅은 Rollup을 통해 직접 번들러를 구성하여, 라이브러리 배포를 진행하는 글로 찾아오겠습니다.

기술 블로그 인생 통틀어 가장 오래 작성한 글이었네요. 당이 많이 떨어졌습니다 ㅎㅎ

잘못된 정보가 있다면 바로 정정하겠습니다.
감사합니다!


참고 

- https://esbuild.github.io/faq/#why-is-esbuild-fast

- https://www.snowpack.dev/concepts/how-snowpack-works

- https://heropy.blog/2020/10/31/snowpack/

- https://www.snowpack.dev/guides/optimize-and-bundle

- https://fe-developers.kakaoent.com/2022/220623-webpack-module-federation/

 

- https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c

- https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c

- https://ui.toast.com/posts/ko_20220127

반응형