[FE][Bundler] Vite

Choise.o·4일 전

FE

목록 보기
1/6
post-thumbnail

FE 프로젝트 스펙이 꽤나 레거시해서 버전 업그레이드 작업이 예정돼있다.

만약 담당하게 된다면 직접 작업해보고
담당하지 않더라도 서포트 하고 싶어 조금씩 리서치 해두려 한다.

지금 상태는 버전 문제도 있지만 프로젝트 규모가 꽤 많이 커져서 콜드스타트 시간이 너-무 오래 걸린다.

빌드 방식도 바꾸고 싶은 욕심이 생겨서 vite 에 대해 알아봤는데
겸사겸사 모듈 시스템번들러 개념도 정리했다.


1. JavaScript 모듈 시스템의 발전 과정

1) 최초의 JavaScript – 짧은 코드만 작성하던 시절

js는 처음부터 대규모 애플리케이션을 염두에 두고 설계된 언어가 아니었다.
대부분 버튼 클릭 시 alert를 출력하거나, 간단한 폼 검증 같은 매우 간단한 작업에만 사용했다.

<script>
  alert('Hello World')
</script>

이 당시에는
파일은 보통 1개였고 / 모든 코드가 전역 스코프에 존재했으며 / 파일 분리나 모듈 시스템 같은 개념이 없었다.

“코드를 나눈다”는 생각 자체가 필요 없었다.


2) CommonJS의 등장

웹이 점점 복잡해지고,
js가 Node.js 기반 서버 환경에서도 사용되기 시작하면서 문제가 발생했다.

파일 하나에 코드가 수천 줄씩 작성되자
코드를 기능 단위로 분리하는 것과 공통 로직을 재사용하고 싶은 니즈가 생겼다.
그래서 등장한 것이 CommonJS다.

[CommonJS]

// mathUtils.js
function sum(a, b) {
  return a + b
}

module.exports = { sum }
// main.js
const { sum } = require('./mathUtils')

console.log(sum(1, 2))

CommonJS는

  • require, module.exports를 사용하고
  • 동기적 로딩 방식을 사용했으며
  • Node.js 환경에서는 매우 잘 동작했다.

서버에서는 파일을 디스크에서 바로 읽을 수 있기 때문에, 동기 로딩 방식이 큰 문제가 되지 않았다.


3) CommonJS의 문제점과 AMD의 등장

CommonJS를 브라우저 환경에 그대로 적용하려고 하면 문제가 발생한다.

브라우저는 파일 시스템 접근이 불가능해서 모든 js 파일은 네트워크 요청을 거쳐야 한다. 이때 require() 기반의 동기 로딩은 치명적이다.

const config = require('./config') // 동기 방식이라서 네트워크 응답을 기다려야 함

이런 코드가 여러 개 쌓이면 페이지 로딩 속도가 급격히 느려진다.
그래서 등장한 방식이 AMD (Asynchronous Module Definition) 이다.

[AMD]

define(['mathUtils'], function (mathUtils) {
  console.log(mathUtils.sum(1, 2))
})

AMD 방식은

  • 브라우저 환경에 맞게 비동기 로딩 방식을 사용한다
  • 필요한 모듈을 동시에 요청해서
  • 로딩 완료 후 콜백을 실행한다

4) AMD의 문제점

그러나 AMD 방식도 실무에 적용해보니 문제가 있었다.

문제 1) 네트워크 요청이 너무 많다
js 파일 하나당 HTTP 요청 1번이 수행되는데 파일 수가 많아지면 그만큼 요청이 많아진다.

// 이런 구조라면? 최소 5번 이상의 request가 발생하여 네트워크 비용이 증가한다
main.js
 ├─ authService.js
 ├─ userApi.js
 ├─ validationUtils.js
 ├─ dateUtils.js

문제 2) 응답이 하나라도 늦으면 전체 실행이 늦어진다

define(['authService', 'userApi', 'dateUtils'], function (...) {
  // 하나라도 늦으면 실행 지연
})

문제 3) 코드 가독성과 유지보수성이 좋지 않다.
콜백 기반 구조여서 / 모듈이 많아질수록 중첩이 증가하고 / 개발 경험(DX)가 안좋아진다.

결과적으로 AMD도 범용화되지 못했고 번들러가 등장했다.


5) 번들러

다양한 시행착오 끝에 개발자들은 하나의 결론에 도달했다.

“개발할 때는 나누고, 배포할 때는 하나로 묶자”

이 역할을 수행하는 도구가 번들러(Bundler) 다.

번들러를 간단하게 도식화하면 아래와 같다.

개발 중
 ├─ authService.js
 ├─ userApi.js
 ├─ dateUtils.js

배포 시
 └─ bundle.js
// bundle.js (개념적 예시)
(function () {
  function sum(a, b) {
    return a + b
  }

  console.log(sum(1, 2))
})()

네트워크 요청 최소화하면서 브라우저 로딩 성능 개선하고
모듈 시스템의 단점을 보완할 수 있다. 번들러에 대해 좀 더 자세하게 알아보자.


2. Bundler

1) 번들러란

번들러는 단순히 파일을 합치는 도구가 아니다.
주요 역할은 다음과 같다.

  • 여러 JS 파일을 하나로 묶음
  • 의존성 관계 분석
  • 사용하지 않는 코드 제거 (Tree Shaking)
  • 브라우저 호환을 위한 변환

대표적으로 Webpack, Rollup, Parcel, Vite 가 있다.


2) Webpack

Webpack은 사실상 오랫동안 프론트엔드 번들러의 표준이었다.

Webpack의 기본 동작 방식은

  1. 엔트리 파일부터 시작
  2. 모든 import를 따라가며 의존성 그래프 생성
  3. 전체 코드를 하나의 번들로 묶음
  4. 그 결과로 개발 서버 실행
// entry.js
import App from './App'
import Header from './components/Header'

모든 의존성을 하나의 번들로 처리하기 때문에

  • 프로젝트가 커질수록 초기 빌드 시간이 오래 걸리고
  • 개발 서버 시작이 느려진다
  • 게다가 파일 하나만 수정해도 다시 번들링해서 HMR은 가능하지만 점점 무거워진다.

이 상황을 체감하고 있는 입장에서 DX가 매우 좋지 않다는 것에 공감한다🥲


3) Vite

Vite는 이런 질문에서 출발했다.

“왜 개발 중에도 전체를 번들링해야 할까?”

Vite의 주요 특징으로는

  • 개발 중에는 번들링을 하지 않는다
  • 브라우저의 ES Module(ESM) 기능을 적극 활용한다
  • 필요한 파일만 즉시 요청해서 로드한다

vue 프로젝트를 예시로 들면
<!-- index.html -->
<script type="module" src="/main.js"></script>
// main.js
import App from './App.js'
  1. 브라우저가 index.html을 읽다가
  2. <script type="module" src="/main.js">를 발견하면 main.jsES Module 방식으로 실행한다
  3. main.js 파일을 해석하다가 import App from './App.js' 코드를 만나면
  4. “이 파일은 App.js에 의존하고 있구나”라고 판단하고
    App.js추가로 네트워크 요청한다

ESM?
JavaScript의 공식 표준 모듈 시스템이다.

  • import/export 문법을 사용
  • 브라우저가 모듈 간 의존 관계를 직접 해석한다
  • 필요한 파일을 자동으로 네트워크 요청

ESM 을 활용하면 번들러가 모든 파일을 미리 묶지 않아도, 브라우저가
모듈 로딩을 직접 처리할 수 있다.
Vite는 이 ESM 지원을 전제로 설계된 개발 도구다.

Vite는 “개발 중에는 번들링을 하지 않는다” 구조덕에 아래와 같은 장점을 갖는다.

  • 개발 서버 시작 속도가 매우 빠름
  • 수정한 파일만 즉시 반영됨
  • HMR 성능이 우수함
  • 설정이 단순하고 직관적

번들 기반 개발 서버(ex. webpack)

ESM 기반 개발 서버(Vite)


3-1) Vite의 빌드 방식... Rollup

Vite는 개발 단계와 빌드 단계를 명확히 분리한다.

  • 개발 중
    → ESM 기반, 번들링 X
  • 배포용 빌드
    → 번들링 O

배포 시에는 내부적으로 Rollup을 사용해 최적화된 결과물을 생성한다.

Rollup?

  • ESM 기반 번들러
  • Tree Shaking에 강함
  • 불필요한 코드를 제거한 깔끔한 번들 생성에 적합

개발 속도는 ESM으로, 빌드 결과물의 품질은 Rollup으로 보장한다.

3-2) 의존성 Pre-bundle... esbuild

Vite는 개발 서버를 시작할 때
node_modules에 있는 외부 라이브러리들을 대상으로 Pre-bundle 과정을 수행한다.

직접 작성한 소스 코드(src/ 내부)는 개발 중에 수시로 수정되고 HMR 대상이기 때문에
번들링하지 않는 것이 유리하지만

외부 라이브러리 코드는 변동성이 없으므로

  • 수많은 작은 의존성 모듈 요청을 줄이고
  • 브라우저가 처리하기 쉬운 형태로 미리 정리해놓기 위해 번들링 해놓는다.

이 Pre-bundle 단계에서 esbuild가 사용된다.

esbuild?

  • Go 언어로 작성된 초고속 빌드 도구
  • JavaScript / TypeScript 변환 속도가 매우 빠름

Vite는 esbuild를 활용해 초기 로딩 성능을 개선하고 개발 서버 응답 속도 향상시킨다.

정리하면 Vite는

  • ESM을 활용한 개발 서버 구조
  • Rollup 기반의 안정적인 빌드
  • esbuild를 이용한 빠른 Pre-bundle

을 조합하여 기존 번들러의 한계를 보완한 현대적인 프론트엔드 개발 도구라고 볼 수 있다.


4) Webpack과 Vite 차이점

항목WebpackVite
개발 방식전체 번들 후 실행ESM 기반, 필요 시 로드
초기 속도느림매우 빠름
HMR가능하지만 무거워질 수 있음빠르고 가벼움
설정 난이도높음낮음
적합한 경우레거시, 복잡한 설정신규 프로젝트

그래서 vite가 무조건적인 정답이냐고 한다면 그건 아니다.
webpack 은 복잡한 설정을 컨트롤 할 수 있고 긴 사용기간만큼 안정적이다.
프로젝트 성격에 따라서 적합한 번들러를 선택하면 된다.

출처

0개의 댓글