[Web][Next.js] turbopack

이번 포스트에서는 Next.js 에서 사용하는 turbopack 이 무엇인지, 어떤 특징을 갖고 있고 왜 빠르게 동작하는지 알아본다.

공식문서 app router 15.5.4 버전을 기준으로 작성하였다.

 

미리 알아두면 좋은 것들

더보기

bundler

webpack

 

turbopack 이란?

turbopack 이란 JS, TS 위해 최적화된 번들러이다. Rust로 작성되었고, Next.js 에 내장되어 있다. 로컬 개발 환경에서 훨씬 빠른 개발 경험을 제공해 준다.

라고 공식 문서에 설명되어 있다. 참고

 

번들러에는 webpack, rollup, vite ... 등등 많은 종류가 있고 각각 장단점이 있다. 그중에 turbopack 은 next.js 에서 공식적으로 제공해 주고 있는 next.js 에 특화된 번들러이다.

 

 

Next.js 프로젝트를 create-next-app을 사용해 처음 생성할 때  turbopack을 사용할지 말지 선택할 수 있다.

기본적으로 recommended로 사용을 권장하고 있다. Yes를 체크하고 package.json을 확인해 보면 옵션이 붙어있는 것을 볼 수 있다.

"scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack",
    "start": "next start",
    "lint": "eslint"
},

15.5.3의 공식문서를 기준으로, dev 환경에선 stable 상태이고, build 환경에선 alpha 상태이다.

뒤 옵션을 빼고 실행할 경우 기본적으로 webpack을 사용한다.

 

나는 신규 프로젝트애서 로컬 실행 시에만 해당 옵션을 적용해 보았다. 확실히 이전 프로젝트에서 webpack을 사용할 때 보다 빠르다고 느껴졌다.

프로젝트 규모는 완전히 같진 않지만 비슷한 수준이다. turbopack을 적용한 신규 프로젝트는 1초 이내에 실행되었고, 기존 프로젝트는 2~3초 정도 걸렸다. 확실히 빠른 게 체감이 된다.

공식 문서상으로 webpack을 사용할 때보다 45.8% 빨라졌다고 한다.

코드를 수정하고 hot reload 되는 속도도 빠르다.

공식 문서상으로 webpack을 사용할 때보다 96.3% 빨라졌다고 한다.

https://nextjs.org/blog/turbopack-for-development-stable

 

turbopack 은 어떻게 이렇게 빠르게 빌드해서 실행되는 걸까?

 

특징

Why Turbopack? [공식 문서]

공식문서의 설명을 해석해 보았다.

 

 

Unified Graph: next.js 는 client와 server처럼 여러 출력 환경을 지원한다. 여러 컴파일러를 관리하고 번들을 함께 붙여 쓰는 것은 번거롭기 때문에, turbopack 은 모든 환경에서 하나의 unified graph를 사용한다.

 

Bundling vs Native ESM: 일부 도구는 개발환경에서 번들링을 생략하고, 브라우저의 native ESM에 의존한다. 작은 app 에선 잘 동작하지만, 큰 규모의 app 에선 과도한 네트워크 요청 때문에 느려질 수 있다. turbopack은 개발에서도 번들을 수행하되, 큰 규모의 앱에서도 빠르게 번들링 되도록 최적화된 방식을 사용한다.

 

Incremental Computation: turbopack은 여러 코어(cpu)에 작업을 병렬화하고 함수 수준까지 결과를 캐시 한다. 한 번 처리된 작업은 다시 반복하지 않는다.

더보기

webpack 은 CPU를 통한 병렬화는 지원하지 않는다. 효율적인 병렬화를 위해 데이터는 스레드 간 쉽게 접근이 가능해야 하기 때문이다.

 

Lazy Bundling: turbopack은 개발 서버가 실제로 요청한 것만 번들링 한다. 이 지연 방식은 초기 컴파일 시간과 메모리 사용량을 줄여준다.

 

- webpack 과는 다른 번들링 방식

- Rust라는 언어 덕분에 가능한 CPU 레벨에서의 병렬화

- layze bundling

등의 이유 때문에 빠른 것이다.

 

Unified graph

다른 것들은 어느 정도 이해되는데 unifed graph 가 무엇인지 잘 이해가 안 되었다. unified graph 때문에 빨라지는구나! 가 핵심인 것 같은데, 이 용어를 처음 보기 때문에 그래서 그게 뭔데 어떻게 빨라지게 되는 건데?라는 생각이 들어 관련해서 더 찾아봤다.

 

https://nextjs.org/blog/turbopack-for-development-stable?utm_source=chatgpt.com#highlights

여기서 그 해답을 찾을 수 있었다. 궁금하다면 highlights 부분은 전부 읽어보는 것을 추천한다.

궁금증을 가졌던 부분의 핵심내용만 가져와보았다.

 

----------------------------------------------------------------------------------------------------------------

 

webpack 같은 번들러는 내부적으로 굉장히 많은 일을 수행한다. 라우트를 처음 컴파일할 때, 번들러는 entrypoint부터 시작한다. next.js의 경우 entrypoint는 page.tsx와 해당 라우트와 관련된 모든 파일의 조합을 말한다.

 

이 entrypoint들이 파싱 되어 imprt 문을 찾고, 각 import는 다시 파일로 resolve 되어 같은 방식으로 처리한다. 이 과정을 더 이상 가져올 import가 없을 때까지 반복하면 module graph가 만들어진다.

 

module graph는 단순히 JS/TS 모듈뿐만 아니라, node_modules 안의 라이브러리, 전역 css, css 모듈 파일, next/image로 불러오는 정적 이미지 파일 같은 리소스까지 포함될 수 있다.

 

모든 모듈이 수집되면 chunk라고 불리는 JS 번들을 생성한다. 이 chunk들은 빌드타임, 또는 런타임에 서버에서 실행되거나, 브라우저에서 실행되는 최종 산출물이다.

 

하지만 webpack은 여러 환경에서(서버, 브라우저) 실행될 출력을 한 번에 만드는 그래프를 지원하지 않는다. 즉 최소 두 개의 별도 컴파일러를 실행해야 한다!

 

그 과정은 다음과 같다.

우선 서버용 module graph를 먼저 컴파일해야 "use client" 지시문을 가진 모듈을 모두 찾을 수 있다. 서버 빌드가 끝나면, 그 그래프를 순화화면서 브라우저용 컴파일러에서 필요한 entrypoint를 만들어낸다.

 

브라우저용 컴파일러는 완전히 별개의 webpack 컴파일러이기 때문에 이 과정에서 코드가 클라이언트, 서버에서 두 번 파싱 되는 등 여러 추가적인 오버헤드가 발생한다!

 

turbopack은 이런 문제를 해결하고 싶었다. 그 방법이 컴파일러가 여러 다른 환경(서버, 클라이언트)을 직접 인식하도록 하는 것이다. 내부적으로 이런 대상 간의 전환을 transition이라고 부른다. 특정 import를 서버 -> 브라우저로의 전환, 또는 브라우저 -> 서버로의 전환으로 표시해 둘 수 있다. 이 덕에 turbopack은 서버 컴포넌트와 클라이언트 컴포넌트, 클라이언트 컴포넌트에서 import 한 서버 함수까지도 효율적으로 번들링 할 수 있게 되었다.

 

----------------------------------------------------------------------------------------------------------------

 

기존에 webpack 에선 클라이언트, 서버 번들링을 위해서 컴파일 과정이 두 번 발생하고 관련된 오버헤드가 있어 여러 module graph 가만 들어졌었는데, turbopack 은 이를 unified graph로 만들어 빌드시간을 줄인 것이다!

 

 

webpack 과의 차이

Turbopack은 다른 방식으로 순서가 정해지지 않은 CSS 모듈의 경우, JS의 import 순서를 따라 정렬한다.

import utilStyles from './utils.module.css'
import buttonStyles from './button.module.css'
export default function BlogPost() {
  return (
    <div className={utilStyles.container}>
      <button className={buttonStyles.primary}>Click me</button>
    </div>
  )
}

turbopack 에선 utils.module.css 가 먼저 CSS chunk를 생성하는 것이 보장되어 있다.

webpack 도 일반적으론 동일하게 동작하지만, 무시하는 경우가 존재한다. 예를 들어 해당 JS 파일이 side-effect-free 하다고 추론할 경우이다.

 

이럴 때 turbopack을 사용하는 경우 미묘하게 렌더링이 변화될 수 있다.

 

turbopack에선 아직 webpack에 있는 Inner Graph Optimization 같은 기능이 없다.

 

turbopack에선 아직 webpack에서 제공하는 disk build caching과 같은 기능이 없다.

 

turbopack에선 webpack plugins를 지원하지 않는다.

 

성능적으로는 turbopack이 훨씬 우수하지만, 세부적으로 여러 설정들은 아직 webpack 만큼의 다양성은 없는 것 같다. 또한 아직 지원되지 않는 기능들도 많아서 안정적으로 운영환경을 위한 빌드를 하기 위해선 넘어야 할 산이 많아 보인다.

 

다만 개발환경에서 만큼은 정말 빠르기 때문에 한번 도입해 보는 것도 나쁘지 않아 보인다. 특히 신규로 프로젝트를 개발하는 경우에 사용하면 좋을 것 같다. 배포를 위해 빌드할 때는 결국 webpack을 사용해야 하기 때문에 조금 번거로울 수도 있지만... 개발환경에서 실행할 때 속도가 빠른 점은 번거로움을 감내할 만큼 편리한 것 같다.

 

글이 거의 해석본에 가까운 내용이지만, 궁금했던 점들은 많이 해소된 것 같다.

'웹(Web)' 카테고리의 다른 글

thymeleaf 정리 (작성중..)  (1) 2024.01.31
쿠키, 세션, 토큰, JWT (Cookie, Session, Token, JWT)  (0) 2022.07.23