웹팩(Webpack), 롤업(Rollup)과 같은 자바스크립트 번들러의 필요성
들어가며: 모던 웹 개발의 복잡성
오래전 웹 개발에서는 HTML 파일에 몇 개의 JS 파일만 추가하고 끝났던 시절이 있었다고 합니다. 하지만 오늘날의 웹 개발은 어떤가요? React, Vue, Angular와 같은 프레임워크, TypeScript, CSS 전처리기, 수백 개의 npm 패키지... 현대 웹 애플리케이션은 이전보다 훨씬 복잡해졌습니다.
이 복잡성을 다루기 위해 등장한 도구가 바로 자바스크립트 번들러입니다. 웹팩(Webpack), 롤업(Rollup),Vite, Parcel과 같은 도구들이 현대 웹 개발의 복잡성을 관리하는 핵심 역할을 합니다. 오늘은 이 번들러들이 왜 필요한지, 어떤 문제를 해결하는지 깊이 있게 살펴보겠습니다.
목차
- 번들러란 무엇인가?
- 번들러가 해결하는 핵심 문제
- 웹팩(Webpack)의 주요 기능
- 롤업(Rollup)의 주요 기능
- 번들러의 진화: ESBuild와 SWC
- 어떤 번들러를 선택해야 할까?
- 번들러 없이 개발할 수는 없을까?
- 자주 접하는 문제와 해결 방법
- 번들러 사용의 베스트 프랙티스
번들러란 무엇인가?
자바스크립트 번들러는 간단히 말해 여러 개의 파일을 하나(또는 여러 개의 최적화된 파일)로 묶어주는 도구입니다. 하지만 이 단순한 설명 뒤에는 현대 웹 개발에서 직면하는 수많은 복잡한 문제를 해결하는 강력한 기능들이 숨어 있습니다.
📁 project
├── 📁 node_modules/
│ ├── 📁 react/
│ ├── 📁 lodash/
│ └── 📁 기타 100+ 패키지/
├── 📁 src/
│ ├── app.js
│ ├── components/
│ ├── utils/
│ └── styles/
└── index.html
위와 같은 구조의 프로젝트가 있을 때, 번들러는 이 모든 파일을 분석하고 최종적으로는 브라우저가 이해할 수 있는 형태(ES5 , 폴리필 등)로 변환하여 배포합니다.
번들러가 해결하는 핵심 문제
1. 모듈 시스템의 다양성 문제
자바스크립트는 오랫동안 공식적인 모듈 시스템이 없었습니다. 이로 인해 다양한 모듈 형식이 등장했죠:
- CommonJS (Node.js 환경):
require()
및module.exports
- AMD (RequireJS): 비동기 모듈 정의
- UMD: CommonJS와 AMD를 모두 지원하는 방식
- ES Modules: 현대 자바스크립트의 공식 모듈 시스템 (
import
/export
)
아래는 각 모듈 시스템의 간단한 예시입니다:
// CommonJS (Node.js)
const lodash = require('lodash');
module.exports = function doSomething() {...};
// ES Modules
import lodash from 'lodash';
export function doSomething() {...};
브라우저는 기본적으로 이러한 모듈 문법을 모두 이해하지 못합니다(최신 브라우저는 ES Modules를 지원하지만, 구형 브라우저는 지원하지 않음). 번들러는 이러한 다양한 모듈 형식을 브라우저가 이해할 수 있는 형태로 변환합니다.
2. HTTP 요청 최소화
웹 성능에 영향을 미치는 주요 요소 중 하나는 HTTP 요청의 수입니다. 수많은 작은 JS 파일을 로드하는 것보다, 하나의 큰 파일(또는 최적화된 여러 개의 파일)을 로드하는 것이 일반적으로 더 효율적입니다.
<!-- 번들러 사용 전 -->
<script src="app.js"></script>
<script src="utils.js"></script>
<script src="components/button.js"></script>
<script src="components/form.js"></script>
<script src="node_modules/lodash/index.js"></script>
<!-- 등등 수십 개의 스크립트 -->
<!-- 번들러 사용 후 -->
<script src="bundle.js"></script>
<!-- 또는 최적화된 여러 개의 번들 -->
<script src="vendor.js"></script>
<script src="app.js"></script>
3. 최신 자바스크립트 문법 지원
ES6+ 문법은 개발자 경험을 크게 향상시키지만, 모든 브라우저가 이러한 최신 문법을 지원하는 것은 아닙니다. 번들러는 Babel과 같은 도구와 연동하여 최신 자바스크립트 코드를 구형 브라우저에서도 작동하는 코드로 변환(트랜스파일)합니다.
// 자바스크립트 코드 (ES2023)
export function sortProducts(products) {
// 원본 배열을 변경하지 않고 정렬된 새 배열 반환
const sortedByPrice = products.toSorted((a, b) => a.price - b.price);
return sortedByPrice;
}
// 사용 예시
const products = [
{ id: 1, name: "Phone", price: 699 },
{ id: 2, name: "Laptop", price: 999 },
{ id: 3, name: "Earbuds", price: 129 },
{ id: 4, name: "Monitor", price: 249 }
];
console.log(sortProducts(products));
// 바벨이 ES2023의 toSorted() 메서드를 트랜스파일하면, 폴리필 형태의 코드는 대략 다음과 같을 것입니다
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sortProducts = sortProducts;
require("core-js/modules/es.array.to-sorted.js");
function sortProducts(products) {
// 바벨은 toSorted를 직접 사용하지 않고 폴리필로 완전히 대체함
var sortedByPrice = products.slice().sort(function (a, b) {
return a.price - b.price;
});
return sortedByPrice;
}
4. 코드 최적화 및 분석
번들러는 단순히 파일을 연결하는 것 이상의 작업을 수행합니다. 트리 쉐이킹(사용하지 않는 코드 제거), 코드 분할(code splitting), 미니피케이션(공백 및 주석 제거), 번들 크기 분석 등 다양한 최적화 작업을 수행합니다.
// 원본 코드
import { sum, multiply, divide } from './math.js';
// 아래 함수 중 sum만 사용됨
console.log(sum(1, 2));
// 트리 쉐이킹 후 번들에 포함되는 코드
// multiply와 divide 함수는 번들에서 제외됨
웹팩의 주요 기능
웹팩(Webpack)은 가장 인기 있는 번들러 중 하나로, 풍부한 기능과 생태계를 갖추고 있습니다.
1. 로더(Loader) 시스템
웹팩의 가장 강력한 기능 중 하나는 다양한 파일 형식을 처리할 수 있는 로더 시스템입니다.
// webpack.config.js 간단 예시
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader' // JS 파일은 Babel로 처리
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // CSS 파일 처리
},
{
test: /\.(png|jpg|gif)$/,
use: 'file-loader' // 이미지 파일 처리
}
]
}
};
이 설정을 통해 자바스크립트 파일뿐만 아니라 CSS, 이미지 등 모든 종류의 애셋을 모듈로 다룰 수 있습니다.
2. 플러그인(Plugin) 시스템
웹팩의 플러그인 시스템은 번들링 과정의 거의 모든 측면을 커스터마이징할 수 있게 해줍니다.
// webpack.config.js에 플러그인 추가
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// 기본 설정...
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html' // HTML 파일 자동 생성
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css' // CSS 파일 분리 추출
})
]
};
3. 코드 분할(Code Splitting)
웹팩은 애플리케이션을 여러 청크(chunk)로 분할하여 필요할 때만 로드할 수 있게 해줍니다.
// 동적 임포트를 통한 코드 분할
import React from 'react';
const App = () => {
const [ShowComponent, setShowComponent] = React.useState(null);
const loadComponent = async () => {
// 이 부분은 별도의 청크로 분할됩니다
const { default: LazyComponent } = await import('./LazyComponent');
setShowComponent(() => LazyComponent);
};
return (
<div>
<button onClick={loadComponent}>컴포넌트 로드</button>
{ShowComponent && <ShowComponent />}
</div>
);
};
롤업의 주요 기능
롤업(Rollup)은 특히 라이브러리 개발자들 사이에서 인기 있는 번들러입니다.
1. 트리 쉐이킹(Tree Shaking) 최적화
롤업은 ES 모듈의 정적 구조를 활용하여 효율적인 트리 쉐이킹을 수행합니다. 이는 라이브러리 크기를 최소화하는 데 특히 유용합니다.
// rollup.config.js 간단 예시
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'esm' // ES 모듈 형식으로 출력
}
};
2. 다양한 출력 형식 지원
롤업은 다양한 모듈 형식으로 출력을 생성할 수 있어 라이브러리 개발에 유리합니다.
// rollup.config.js에서 여러 형식 출력
export default {
input: 'src/main.js',
output: [
{
file: 'dist/bundle.cjs.js',
format: 'cjs' // CommonJS
},
{
file: 'dist/bundle.esm.js',
format: 'esm' // ES Modules
},
{
name: 'myLibrary',
file: 'dist/bundle.umd.js',
format: 'umd' // UMD (브라우저와 Node.js 모두에서 사용 가능)
}
]
};
번들러의 진화: ESBuild와 SWC
최근에는 성능에 중점을 둔 새로운 번들러들이 등장하고 있습니다.
ESBuild
Go 언어로 작성된 ESBuild는 웹팩보다 10-100배 빠른 속도를 자랑합니다.
// esbuild 간단 사용 예시
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
outfile: 'dist/bundle.js',
}).catch(() => process.exit(1));
SWC (Speedy Web Compiler)
Rust로 작성된 SWC는 Babel의 대안으로, 훨씬 빠른 속도로 코드를 트랜스파일합니다.
// Next.js 12에서 SWC 사용 예시 (next.config.js)
module.exports = {
swcMinify: true, // SWC를 사용하여 JavaScript 코드 압축
};
어떤 번들러를 선택해야 할까?
번들러 선택은 프로젝트의 특성과 요구사항에 따라 달라집니다.
웹팩을 선택해야 하는 경우
- 복잡한 애플리케이션을 개발할 때
- 다양한 자산 유형(CSS, 이미지 등)을 처리해야 할 때
- 풍부한 생태계와 커뮤니티 지원이 필요할 때
롤업을 선택해야 하는 경우
- 라이브러리 개발 시
- 트리 쉐이킹이 중요할 때
- 다양한 모듈 형식으로 출력해야 할 때
ESBuild/SWC를 선택해야 하는 경우
- 빌드 속도가 가장 중요한 요소일 때
- 큰 프로젝트에서 개발 경험을 향상시키고 싶을 때
번들러 없이 개발할 수는 없을까?
ES 모듈은 현대 브라우저에서 기본적으로 지원되므로, 이론적으로는 번들러 없이 개발이 가능합니다.
<!-- 번들러 없이 ES 모듈 사용 -->
<script type="module">
import { Component } from './components.js';
import { helper } from './utils.js';
// 코드 작성...
</script>
하지만 이 방식에는 여러 한계가 있습니다:
- HTTP 요청 수가 증가하여 성능이 저하될 수 있음
- 구형 브라우저 지원 문제
- 트리 쉐이킹과 같은 최적화 부재
- npm 패키지 직접 사용의 어려움
따라서 현대 웹 애플리케이션에서는 여전히 번들러가 필요한 경우가 대부분입니다.
자주 접하는 문제와 해결 방법
문제 1: 번들 크기가 너무 큰 경우
원인: 너무 많은 의존성, 큰 라이브러리, 최적화 부족
해결 방법:
- 코드 분할(Code Splitting) 적용
- 트리 쉐이킹 활성화
- 불필요한 의존성 제거
- 번들 분석 도구 사용 (예: webpack-bundle-analyzer)
// webpack-bundle-analyzer 사용 예시
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
// 기존 설정...
plugins: [
new BundleAnalyzerPlugin()
]
};
문제 2: 빌드 속도가 느린 경우
원인: 많은 모듈, 복잡한 로더 체인, 무거운 플러그인
해결 방법:
- 캐싱 활성화
- 병렬 처리 설정
- 필요한 로더와 플러그인만 사용
- ESBuild나 SWC와 같은 빠른 번들러로 전환 고려
// 웹팩 캐싱 및 병렬 처리 설정
module.exports = {
// 기존 설정...
cache: true,
parallelism: 4,
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
options: {
cacheDirectory: true // Babel 캐싱 활성화
}
}
]
}
};
번들러 사용의 베스트 프랙티스
권장 사항
- 환경별 최적화: 개발 환경과 프로덕션 환경을 분리하여 설정
- 코드 분할 적극 활용: 초기 로드 시간 개선을 위해 필요한 코드만 로드
- 캐싱 전략 수립: 효율적인 캐싱을 위한 콘텐츠 해시 활용
- 정기적인 의존성 업데이트: 보안 및 성능 개선을 위해 정기적으로 패키지 업데이트
// 환경별 웹팩 설정 예시
const commonConfig = {
// 공통 설정
};
const devConfig = {
mode: 'development',
devtool: 'inline-source-map',
// 개발 환경 특화 설정
};
const prodConfig = {
mode: 'production',
optimization: {
splitChunks: {
chunks: 'all'
}
},
// 프로덕션 환경 특화 설정
};
module.exports = (env) => {
return env.production ? { ...commonConfig, ...prodConfig } : { ...commonConfig, ...devConfig };
};
용어 설명
- 번들링(Bundling): 여러 개의 파일을 하나 또는 여러 개의 최적화된 파일로 묶는 과정
- 트리 쉐이킹(Tree Shaking): 사용하지 않는 코드를 제거하는 최적화 기법
- 코드 분할(Code Splitting): 코드를 여러 개의 청크로 나누어 필요할 때만 로드하는 기법
- 로더(Loader): 다양한 유형의 파일을 모듈로 변환하는 웹팩의 기능
- 플러그인(Plugin): 번들링 과정의 다양한 측면을 확장하는 기능
- 청크(Chunk): 번들러가 생성하는 코드의 단위
- 진입점(Entry Point): 번들러가 의존성 트리를 구축하기 시작하는 파일
- ESM(ECMAScript Modules): 자바스크립트 공식 모듈 시스템
- CJS(CommonJS): Node.js 환경에서 주로 사용되는 모듈 시스템
- 트랜스파일링(Transpiling): 한 형태의 소스 코드를 다른 형태로 변환하는 과정
Concepts | 웹팩
웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.
webpack.kr
https://2022.stateofjs.com/en-US/libraries/build-tools/
State of JavaScript 2022: Build Tools
This chart splits positive (“want to learn”, “would use again”) vs negative (“not interested”, “would not use again”) experiences on both sides of a central axis. Bar thickness represents the number of respondents aware of a technology.
2022.stateofjs.com