개발자
류준열
Turbopack과 Vite

Next로 만든 랜딩페이지들이 개발환경에서 코드 변경 후 저장하면 5초가 지나고 반영되는 걸 종종 경험했다.
Turbopack의 번들링 때문이라기보다는 규모와 리소스 차이 때문이라고 느꼈는데, Webpack기반의 CRA로 만든 비슷한 규모의 리액트에서는 개발환경의 불편함을 크게 느끼지 못했기 때문이다. 아마 랜딩페이지의 느린 개발환경은 assets들과 서버단에서의 fetching 때문이었지 않을까 싶다.
그래서 아마 프로젝트의 속성 때문이었지 않을까 싶다.
이와 관련해서 알아보다가 Next에서 Tanstack Start로 갈아탄 누군가의 글을 보게 되었고, 둘의 차이를 조사하다가 근본인 Turbopack과 Vite에 대한 내용을 정리하게 되었다. 재미있는건 양쪽 다 서로를 'some bundler', 'some tool' 라며 서로의 약점을 언급하고, 자신들이 더 우위에 있다는 것을 내세우고 있었다.

번들링
브라우저가 이해하는건 3개다
- html
- css
- js
그런데 내가 작성하는 코드는 TS, React(jsx), tailwind, ... ,등등이다.
브라우저는 이걸 하나도 못읽는다. 그래서 중간에 변환기가 필요한데 이게 트랜스파일러와 번들러다.
그러다 Next에서는 rust로 만든 turbopack이 나오면서 번들링을 아주 빠르게 처리하고, vite는 자주 변경되는 소스코드는 브라우저 네이티브 ESM을 이용하는 컨셉으로 세상에 등장했다.
Turbopack
웹팩과 같은 역할을 하는 번들러다. rust로 만들어서 번들링을 아주 빠르게 하고 Next에 최적화되어 있다.
문제는 번들링은 전체 코드를 묶어주는거라 결국 전체를 읽게 되고 프로젝트가 비대해질수록 개발 환경 구동이 오래 걸리게 된다.
이를 해결하기 위해 Turbopack은 Incremental Computation, Lazy Bundling을 도입했다. (공식문서)
- Incremental Computation: 완료된 작업을 캐싱하여 반복하지 않음
- Lazy Bundling: 실제 요청하는 항목만 번들링한다.
Turbopack이 Vite보다 10배 빠르다하는데 관련해서 재미있는 discussion도 있다.
Vite
한편 Vite는 브라우저 네이티브 기능을 그대로 사용한다.
'ES6에서는 브라우저가 import를 이해할 수 있으니 import를 타고 필요한 파일만 변환해서 주면 번들링 할 필요 없지 않나?' 라는 관점에서 만들어진 도구다.
// App.tsx
import { Header } from './Header'
import { Footer } from './Footer'
..
예를들어 위 코드에서는 브라우저가 import를 타고 필요한 Header, Footer에 접근한다. 이게 ES6의 네이티브 동작이고 Vite는 이걸 활용하여 브라우저가 요청한 파일만 서빙한다.
또한 브라우저 요청에 의해 변환된 파일은 저장되지 않고 메모리에서 처리된다.
단, import waterfall이 발생 할 수 있다.
예를들어 app.js안에 import 10개 있고 각 import안에 또 10개 씩이 있고 .. 연쇄적으로 500번 요청하는 경우 전체를 미리 분석한 Turbopack이 더 빠를 수 있다.
그래서 Vite는 종속성과 소스코드라는 2가지 범주로 나누어 처리한다. (공식문서)
- 종속성: 개발중에 변경되지 않는 코드들(ex: node_modules), 이 코드들은 esbuild를 이용하여 미리 번들링한다
- 소스코드: JSX, CSS, Svelte 컴포넌트 등 자주 수정되는 코드들은 앞서 말한 ESM을 통해 제공한다.
반면 프로덕션에서는 중첩 import로 인한 성능 저하를 방지 하기 위해 rollup으로 전체번들링을 한 후 서빙한다. (공식문서)
참고문서
- Turbopack vs Vite:
- Turbopack 공식문서: https://nextjs.org/docs/app/api-reference/turbopack#why-turbopack
- Vite 공식문서: https://vite.dev/guide/why
- ESM, CJS 비교설명: https://gapal.tistory.com/80