들어가며
현대 웹 개발에서는 번들러가 거의 모든 프로젝트에서 기본적으로 사용됩니다. 하지만 번들러가 정확히 어떤 역할을 하고, 왜 필요한지에 대해 깊이 고민해본 경험은 많지 않았던 것 같습니다. 이전 프로젝트에서도 Webpack이나 다른 도구를 사용했지만, 단순히 "그냥 번들링해주는 도구" 정도로만 생각하고 있었습니다.
그러다 이번 프로젝트에서 Vite를 번들러로 선택하게 되었고, 이를 활용해 최적화 작업까지 진행하며 번들러의 중요성과 webpack과의 차이에 대해 제대로 알아보게 되었습니다. Vite는 Webpack과는 다른 방식으로 동작하며, 특히 빠른 개발 환경을 제공한다는 점에서 많은 개발자들이 주목하고 있는 도구입니다. 이번 기회를 통해 Vite를 처음 사용해본 경험과 더불어, 번들러라는 도구 자체가 어떤 문제를 해결하고 왜 중요한지 정리해보고자 합니다.
이 글에서는 먼저 번들러가 무엇인지와 왜 웹 개발에서 필수적인 도구로 자리 잡았는지 살펴볼 예정입니다. 그리고 Webpack과 Vite를 비교하며 각각의 특징과 장단점을 다룰 것입니다. 또한, Vite가 왜 최근 들어 많은 개발자들 사이에서 주목받고 있는지, 그리고 제가 이번 프로젝트에서 느낀 Vite의 장점과 실제 사용 경험도 함께 공유하고자 합니다.
저처럼 번들러를 단순히 "번들링해주는 도구"로만 여겼던 분들에게 이 글이 번들러에 대한 이해를 넓히는 계기가 되기를 바랍니다. 그리고 Webpack과 Vite 중 어떤 도구가 자신의 프로젝트에 더 적합한지 판단하는 데에도 도움이 되었으면 합니다!
번들러란?
이 번들러는 여러 개로 나뉘어 있는 소스 파일(HTML, Javascript, CSS, image 등)을 하나의 파일 또는 몇 개의 묶음으로 병합해서 브라우저가 효율적으로 읽고 실행할 수 있도록 만들어주는 도구입니다.
그런데 왜 이런 파일들을 하나로 묶거나 묶음들(bundles)로 나눠야할까요?
몇 가지 이유가 있습니다!
1. 네트워크 요청 최소화
파일이 분산되어 있으면 브라우저는 각각의 파일에 대해 별도의 HTTP 요청을 해야합니다. 현대 웹 애플리케이션에는 js, css, images, font와 같은 엄청난 소스 파일들이 존재하는데, 이 모든 것들을 HTTP 요청해서 받아온다면 페이지 로딩 속도가 엄청 느려지겠죠. 예를 들어 100개의 js파일이 있다면 100번의 요청을 보내야 되는 것입니다.
그래서 번들러를 활용해 이 여러 파일을 하나의 번들로 묶는다면 요청 수를 줄일 수 있습니다. 하나의 큰 파일로 묶으면 브라우저는 한 번의 요청만으로 필요한 모든 코드를 받을 수 있기 때문에, 네트워크 병목 현상을 줄이고, 페이지 로딩 속도가 빨라지게 됩니다.
2. 의존성 관리
모듈화된 코드는 파일 간 의존 관계가 복잡해질 수 있습니다. 리액트에서 하나, 하나의 컴포넌트들은 서로를 참조하고 있는데요. A컴포넌트를 B컴포넌트에서 참조하고, B컴포넌트는 또 C컴포넌트를 참조하는 식으로 얽혀 있으면 브라우저는 이를 처리하기가 어렵습니다.
번들러는 이런 의존성을 분석해서 필요한 파일들을 적절한 순서로 병합해줍니다. 모든 의존 관계가 번들 안에서 해결되기에 브라우저가 직접 이를 처리할 필요가 없습니다. 그래서 코드 실행이 안정적이고, 의존성 문제로 인한 오류가 줄어들게 되죠.
3. 코드 최적화
프로젝트를 개발하다보면 동료 개발자들을 위해 주석을 남기거나, 가독성을 위한 코드 컨벤션의 공백을 추가할 때가 있죠. 이런 주석, 디버깅 코드, 공백, 프로젝트에 사용되고 있지 않는 코드(dead code)가 소스 파일에 남게 된다면 파일 크기가 커지고, 브라우저에서 로드하는데 시간이 오래 걸리게 됩니다.
번들러는 이런 불필요한 코드를 제거하거나 축약해주는 작업을 해줍니다. Tree Shaking이라고 하는 작업 즉, 사용되지 않는 코드를 제거하고, Minification을 통해 공백이나 주석을 제거하거나 축약해주고, css와 js를 합쳐 불필요한 요청을 제거해주기도 합니다. 이런 최적화 작업을 거치면 파일 크기는 줄어들고, 브라우저에서 더 빠르게 로드될 수 있습니다.
4. 브라우저 호환성
악명높은 익스플로러처럼 브라우저마다 지원하는 js문법이나 기능이 다를 수 있습니다. 그래서 babel과 같은 트랜스파일러를 이용해서 최신 문법을 구형 브라우저가 이해할 수 있는 코드로 변환해주는 작업을 하는데요. 단독으로 babel을 쓰게되면 파일마다 각각 변환되지만, 파일이 하나로 병합되지 않기에 각 파일을 브라우저가 개별적으로 요청해야 합니다.
그러나 번들러에서 Babel과 같은 트랜스파일링 작업을 수행한다면 각각의 파일들을 트랜스파일링한 뒤, 하나의 번들로 통합시킬 수 있습니다.
5. 캐싱 효율성
브라우저는 파일을 캐싱해서 재방문 시에 로딩 시간을 단축합니다. 그러나 파일이 여러 개로 나뉘어 있으면 변경된 파일만을 골라 캐싱하기 어렵게 됩니다.
번들러는 이에 유리한 구조를 제공하기 위해서 파일 이름에 해시를 추가해서 변경된 파일만 새로 로드할 수 있도록 해줍니다. 사용자 경험이 개선되고 서버 부하가 줄어들게 되겠죠.
그 외에도 일관된 에셋 관리 등의 번들러를 사용하는 이점들이 많이 있습니다. 결국 번들러는 여러 개의 소스 파일을 하나로 묶어 웹 애플리케이션의 성능과 유지보수성을 향상시키고 있음을 알 수 있습니다. 이렇게 번들러가 무엇인지, 어떤 역할을 하는 도구인지 알게되었습니다. 이제는 번들러의 종류에는 어떤 것들이 있는지 한 번 알아보겠습니다.
Webpack: 오래 지켜온 왕좌👑
webpack은 2012년에 등장한 번들러로 위와 같은 문제들을 해결하기 위해 만들어졌습니다. 초기에는 브라우저 환경에서 모듈 시스템을 지원하지 않는 문제를 해결하기 위한 도구로 시작했지만, 점차 플러그인 시스템과 로더를 통해서 다목적 번들러로 자리 잡았습니다. 웹팩을 사용하려면 설정 파일이 필요합니다.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// Entry: Webpack이 시작할 진입점
entry: './src/index.js',
// Output: 번들링 결과 파일의 위치와 이름
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
// Loaders: CSS, 이미지 등 파일을 처리하는 규칙
module: {
rules: [
{
test: /\.css$/, // CSS 파일 처리
use: ['style-loader', 'css-loader'],
},
],
},
// Development Server: 개발 서버 설정
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
compress: true,
port: 9000, // 서버가 실행될 포트 번호
},
// Mode: 개발 모드(development) 또는 배포 모드(production)
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', // HTML 파일을 기반으로 번들링 결과에 포함
}),
],
};
플러그인과 로더는 webpack의 핵심 구성 요소인데요. webpack은 기본적으로 js와 위와 같은 json 파일만 이해합니다. 그 외의 파일(css, 이미지 등)을 처리하려면 로더가 필요합니다. 로더는 이런 파일들을 처리하거나 변환해서 webpack이 이해할 수 있는 형태로 변환해줍니다. 위의 예시에서는 style-loader와 css-loader가 바로 로더입니다.
- css-loader: CSS 파일을 읽고 JavaScript 코드에서 사용할 수 있도록 변환.
- style-loader: 변환된 CSS를 <style> 태그로 브라우저에 삽입.
플러그인의 역할은 무엇일까요? 로더가 개별 파일을 처리하는 데 집중하는 반면, 플러그인은 번들링 과정 전체를 조작하거나 특정 작업을 수행합니다. 예를 들어, 번들 크기 최적화, HTML 파일 생성, 환경 변수 주입 등이 있습니다. 위의 코드에서는 HtmlWebpackPlugin이 플러그인의 예시입니다. 번들링된 js 파일을 자동으로 포함하는 html 파일을 생성하는 플러그인입니다.
이렇게 webpack은 플러그인과 로더를 통해 모든 유형의 파일을 처리할 수 있으며, 트리 쉐이킹, 코드 분할, Hot Module Replacement(코드 수정 시 페이지 새로고침 없이 변경사항을 실시간으로 반영) 기능을 제공합니다. 그리고 지금까지 많은 개발자들이 사용해왔기에 플러그인 생태계가 풍부해 어떤 프로젝트든 적용이 가능하고, 위에서 살펴본대로 개별 코드 최적화 기능이 강력해서 대규모 프로젝트에 적합합니다.
그러나 복잡한 설정 파일이 필요하고, 각각의 로더와 플러그인에 대한 러닝 커브도 존재하게 됩니다. 또한 Dev 서버가 느리고, 빠른 피드백이 필요한 개발 환경에서는 비효율적일 수 있습니다. 느린 속도의 원인은 webpack의 번들링 방식 때문인데요. webpack은 모듈 간의 의존성 그래프를 구성하고, 모든 파일을 트랜스파일링 및 병합해서 하나의 번들을 생성합니다. 이렇게 전체 번들링을 반복적으로 수행하고, 의존성 그래프와 HMR 과정이 복잡하다보니 Dev 서버가 느려지게 되었습니다. 그리고 Babel과 같이 다소 느린 트랜스파일링 도구를 사용하는 것도 원인 중 하나죠.
Vite: 떠오르는 신흥 강자🦸🏻
그러나 반면 vite는 webpack과 다르게 번들링 없이 개발 서버를 실행하고, 브라우저의 ES Modules(ESM)을 활용해 필요한 파일만 즉시 제공하는 방식으로 동작합니다. 또 ESBuild를 사용해 트랜스파일링 속도도 Webpack보다 빠르기 때문에 개발 환경에서 즉각적인 피드백을 제공할 수 있습니다.
Vite는 Vue.js의 창시자인 Evan You가 webpack의 한계를 극복하고자 만든 번들러로, 2020년에 처음 출시되었습니다. 위에서 언급한 대로, 변경된 파일만 즉시 반영해 빠른 개발 피드백을 제공하는 향상된 HMR 성능을 제공하고, ESBuild를 활용해 js와 ts를 즉시 트랜스파일링해서 속도를 월등히 높였습니다. 그리고 브라우저의 ES Modules를 활용해 번들 없이도 실행 가능하도록 만들었죠. 그리고 웹팩에 비해 설정이 매우 편리합니다. 기본 설정만으로도 대부분의 프로젝트에서 바로 사용이 가능합니다.
그러나 웹팩만큼의 플러그인 생태계는 아직 부족하기도하고, 대규모 프로젝트에서 빌드 시간 차이는 webpack과 비슷한 수준이라고 합니다. 아래에서 제가 이번 프로젝트에서 설정했던 vite config 코드를 공유하며, vite를 선택한 이유에 대해 설명해보겠습니다.
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react(),
visualizer({
open: true, // 빌드 후 자동으로 브라우저에서 열기
filename: 'stats.html', // 출력 파일명
template: 'treemap', // treemap, sunburst, network
gzipSize: true,
brotliSize: true,
}),
],
css: {
postcss: './postcss.config.js',
},
build: {
minify: 'esbuild',
target: 'esnext',
rollupOptions: {
output: {
manualChunks: {
three: ['three'],
rapier: ['@react-three/rapier'],
fiber: ['@react-three/fiber'],
drei: ['@react-three/drei'],
// React 코어
react: ['react', 'react-dom', 'react-error-boundary'],
// 상태관리/데이터
state: ['jotai', '@tanstack/react-query'],
// 애니메이션/UI
animation: ['gsap'],
ui: ['react-toastify', 'leva'],
// 네트워킹
networking: ['socket.io-client'],
},
// 청크 파일명 패턴 설정
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
},
},
sourcemap: false, // 프로덕션 빌드시 소스맵 비활성화
},
});
Vite를 선택한 이유: r3f 게임 프로젝트에 적합한 이유
1. 빠른 개발 환경 제공
개발 중 three.js나 r3f(React Three Fiber)와 같은 큰 라이브러리의 코드 변경 시에도 즉각적으로 반영되어 작업 흐름이 끊기지 않는 번들러가 필요했습니다. 제 프로젝트는 60fps 수준의 3D 렌더링이나 애니메이션 상태를 조정하는 반복적인 작업이 필요하기 때문에, 빠른 HMR을 제공하는 vite가 유리한 선택이라 판단했습니다.
2. 효율적인 청크 관리
three.js와 react three fiber 프로젝트는 대형 라이브러리와 의존성이 많아 번들 크기가 컸습니다. 이 때 vite에서 Rollup의 manualChunks를 사용해 무거운 라이브러리를 효율적으로 분리해 초기 로딩 시간을 줄일 수 있었습니다. 특히 Three.js 프로젝트처럼 대형 라이브러리를 사용하는 경우, Vite는 Webpack보다 빌드 시간이 현저히 짧을 수 있습니다.
3. 설정 간소화
그리고 vite는 프로젝트 초기화와 설정 작업에 webpack과 비교해 적은 시간이 소요되어 프로젝트 핵심 기능 개발에 좀 더 집중할 수 있다고 생각했습니다. 고급 옵션을 로더와 플러그인 없이도 쉽게 추가할 수 있어서 설정의 복잡성이 크게 줄어들었습니다. 예를 들어, 최신 브라우저 기능을 최대한 활동해서 Polyfill 없이 가볍고 최적화된 번들을 생성하게 만드는 옵션을 설정 할 때,
// vite
{
target: 'esnext'
}
// webpack
{
"browserslist": [
"last 2 Chrome versions",
"last 2 Firefox versions",
"not IE 11"
]
}
vite는 한 줄만으로 최신 브라우저를 타겟팅할 수 있는 반면, webpack은 .browserslistrc 또는 targets 옵션을 통해 최신 브라우저를 명시해야합니다. 다소 vite보다 복잡함을 알 수 있습니다.
그리고 이 외에도 ESM을 활용한 초기 로딩 속도, 간단한 플러그인, 유지보수 용이성, React 프레임워크 통합 등으로 생산성을 높일 수 있었기에 vite를 선택하게 되었습니다. 특히, 제 프로젝트는 최신 브라우저만 타겟팅하고, 4주라는 시간내에 개발을 완성해야하는 프로젝트였기에 초기 설정과 개발 속도가 중요했습니다. 그래서 Vite가 webpack보다 제 프로젝트에 맞는 선택이었습니다.
결론
Webpack과 Vite는 각각의 강점과 약점을 가진 도구입니다. Webpack은 복잡한 프로젝트와 고도로 최적화된 프로덕션 빌드에서 강력한 성능을 발휘하지만, 설정의 복잡성과 개발 서버 속도에서 단점이 있습니다. 반면, Vite는 간단한 설정과 빠른 개발 환경을 제공하며, 특히 소규모에서 중형 프로젝트에 적합한 도구로 느껴졌습니다. 이번 프로젝트에서 Webpack 대신 Vite를 선택하면서 가장 크게 느낀 점은 개발 서버의 속도였습니다. 코드 변경 사항이 즉시 브라우저에 반영되니 작업 흐름이 끊기지 않고, 생산성이 크게 향상되었습니다.
또한, 설정 파일이 간단해 초기 설정에 시간을 거의 들이지 않았다는 점도 만족스러웠습니다. Webpack에서는 플러그인과 로더를 추가하고 최적화를 위해 설정 파일을 반복적으로 수정해야 했지만, Vite에서는 기본 설정만으로도 충분히 작업이 가능했습니다. Vite의 장점을 직접 경험하면서 번들러 선택의 중요성을 다시금 깨닫게 되었는데요, 이 글을 읽고 계신 분들도 프로젝트의 규모와 요구 사항에 따라 적합한 툴을 선택하시길 추천드립니다!
'React > React' 카테고리의 다른 글
React 배열에서 왜 key를 지정해줘야할까? (+ React Fiber, 고급테크닉) (0) | 2025.01.19 |
---|---|
[1편] 3D 웹게임 렌더링 최적화: 33FPS -> 61FPS 성능 개선 사례 (0) | 2024.12.18 |
가상 DOM과 실제 DOM의 차이: 정말 성능 향상이 있을까? (1) | 2024.11.10 |
OAuth 로그인 요청의 주체는 어디일까? (클라이언트(React)? 서버(Spring)?) (1) | 2023.10.05 |