최근 우연한 기회로 Vite나 CRA(Create React App)을 사용하지 않고, 직접 개발 환경을 구성해 보며 다소 번거롭지만 의미 있는 경험을 했다.
아직 모든 설정을 완벽히 이해했다고 말하기는 어렵지만, 빌드 도구의 역할과 흐름을 몸으로 익히며 프론트엔드 개발자로서 한 단계 더 성장할 수 있었다고 생각한다.
공부를 하다보니 웹팩이 왜 생겨났고, 무슨 역할을 하는지 내가 이해한 바를 정리해보려고 한다.
한 줄로 정의하자면, "모듈 번들러(Module Bundler)"이다.
말이 좀 어렵게 느껴질 수 있는데, 쉽게 풀면 "지저분하게 흩어져 있는 수많은 파일들(HTML, CSS, JS, 이미지 등)을 번들화하여(모아서), 깔끔하게 정리된 하나의 파일로 만들어주는 도구"라고 보면 된다.

웹팩은 우리 프로젝트에 있는 수백 개의 자바스크립트 파일, 스타일시트, 이미지를 전부 '모듈'로 인식한다. 그리고 이것들을 서로 어떻게 의존하고 있는지 파악한 뒤, 브라우저가 읽기 좋게 딱 하나(혹은 몇 개)의 파일로 묶어준다.
"그냥 HTML에 <script> 태그 여러 개 쓰면 안 되나?"라고 생각할 수 있지만 웹팩 없이 개발하던 시절에는 몇몇 골치 아픈 문제들이 있었다고 한다.
변수 이름이 겹친다 (전역 스코프 오염)
a.js에서 var num = 10이라고 쓰고, b.js에서 var num = 20이라고 썼다고 가정해보자. HTML에서 이 두 파일을 같이 불러오면, 나중에 불러온 파일이 변수를 덮어써버린다. 내 의도와 상관없이 변수 값이 바뀌는 끔찍한 버그가 발생하게 되는 것이다.
인터넷이 느려진다 (HTTP 요청 과부하)
웹 페이지 하나를 띄우는데 자바스크립트 파일 100개를 다운로드해야 한다면? 브라우저는 한 번에 보낼 수 있는 요청 수(보통 6개 정도)가 제한되어 있어서, 나머지 파일들은 줄 서서 기다려야 한다. 결국 페이지 로딩 속도가 느려지고, 사용자는 답답해서 나가버리게 된다.
웹팩은 이 파일들을 하나로 합쳐서(Bundling) 요청 횟수를 획기적으로 줄여주고, 모듈 시스템을 통해 변수 충돌 문제도 깔끔하게 해결해준다.
웹팩 설정 파일(webpack.config.js)은 크게 네 가지 주인공이 이끌어간다. Entry, Output, Loader, Plugin. 이 네 명의 역할만 알면 설정 파일이 보이기 시작한다.
(1) Entry(엔트리)
웹팩이 자원을 묶기 위해 제일 먼저 바라봐야 하는 파일이다. 대게 index.js나 main.js가 그 역할을 한다. 이 파일들부터 시작해서 import된 모듈들을 연이어 찾아낸다.
module.exports = {
entry: './src/index.js' // 시작 지점
};
(2) Output(아웃풋)
작업이 끝난 결과물을 저장할 경로와 파일 이름을 정해주는 곳이다.
module.exports = {
output: {
filename: 'bundle.js', // 결과물 파일 이름
path: path.resolve(__dirname, 'dist') // 저장할 폴더
}
};
(3) Loader(로더)
웹팩은 기본적으로 JS랑 JSON만 읽을 수 있게 설계되어 있다. 그래서 CSS나 이미지, 폰트 같은 파일이 들어오면 에러를 뱉는다. 이때 Loader가 등장해서 JS가 이해할 수 있도록 통역 역할을 해준다.
<style> 태그로 넣어준다.주의할 점! 로더는 오른쪽에서 왼쪽(또는 아래에서 위) 순서로 실행된다.
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // css-loader 먼저 실행 후 style-loader 실행
}
]
}
(4) Plugin(플러그인)
로더가 파일 단위로 변환 작업을 한다면, 플러그인은 번들링 된 결과물 전체를 다룬다. 코드를 난독화해서 용량을 줄이거나, 결과물에 저작권 정보를 넣거나, 빌드할 때마다 기존 결과물 폴더를 청소해주는 등 부가적인 기능을 수행한다.
/dist 폴더를 비워준다.처음엔 웹팩 설정이 막막해보였다. 하지만 "파일을 하나로 합쳐준다", "JS가 아닌 것도 모듈로 만들어준다"는 핵심 원리만 기억하고 있으면, 에러를 만나도 당황하지 않고 로그에 따라 침착하게 해결할 수 있지 않을까!?