프론트엔드 빌드 도구와 번들러 작동 원리
프론트엔드에서 사용하는 빌드 도구란 무엇이고, 번들러라는 건 어떻게 내부에서 동작하나?
웹 공통
개발자가 작성한 코드를 웹 브라우저에서 실제로 동작할 수 있는 최적화된 파일(HTML, CSS, JS)로 변환해주는 자동화 도구
필요성
크게 두 가지 문제를 해결한다.
1. 브라우저가 이해하지 못하는 코드를 변환
- 개발자는 SCSS, TypeScript 등 생산성을 높이는 최신 기술을 사용
- 그러나 브라우저는 이것들을 직접 실행하지 못함
- 빌드 도구는 이 코드들을 표준 CSS, JavaScript로 변환해줌
2. 네트워크 성능 최적화
- 수십 개의 JS 파일과 큰 용량의 이미지 파일을 그대로 올리면 웹사이트 로딩이 느려짐
- 빌드 도구는 파일들을 압축하고, 하나로 묶고, 이미지를 최적화하여 UX를 향상시킴
주요 기능
아래 작업을 순차적으로 처리
1. 의존성 관리
import나require같은 구문을 분석해 여러 파일로 나뉘어 있는 JS 모듈 간의 관계를 파악하고 하나로 합칠 준비를 함
2. 변환
- 컴파일: SCSS, Less 같은 코드를 표준 CSS로 변환
- 트랜스파일: TS나 ES6+의 JS 코드를 구형 브라우저에서도 동작하는 이전 버전(ES5)의 코드로 변환 (e.g. Babel 사용)
3. 번들링
- 여러 개의 JS 파일을 분석하여 하나의 파일로 묶어줌
- 브라우저가 서버에 요청하는 파일의 수가 줄어 로딩 속도 향상
4. 최적화
- 압축/축소: 코드의 공백, 주석, 긴 변수명 등을 제거하여 파일 크기를 줄임
- 이미지/폰트 최적화: 이미지나 폰트 파일을 압축하거나 웹에 최적화된 포맷으로 변환
5. 개발 서버 및 Hot Module Replacement
- 개발 중에 코드를 저장할 때마다 자동으로 브라우저 새로고침
- 페이지 전체를 새로고침하는 게 아닌 변경된 부분만 실시간으로 교체(HMR)하여 효율 극대화
Webpack vs Vite
Webpack
-
번들러 기반
-
개발 서버를 시작할 때, 모든 코드를 미리 다 분석, 연결하고 하나의 (또는 몇 개의) 큰 파일로 묶는 번들링 과정 수행
-
장점: 모든 것을 미리 묶어두었기 때문에 배포 환경에서의 최적화에 강력
-
단점: 프로젝트가 커질수록 개발 서버 시작 시간이 매우 길어짐
- 코드를 조금만 수정해도 전체 번들을 다시 계산해야 해서 HMR 속도도 느려진다
Vite
-
네이티브 ES 모듈 기반
-
개발 서버를 시작할 때, 모든 파일을 미리 묶지 않는다
- 대신 브라우저가 소스 코드를 직접 요청하면, 그 순간 필요한 파일만 즉시 변환하여 제공
-
장점: 사전 번들링이 없으므로 개발 서버 시작이 정말 빠르다, 코드 수정 시에도 수정한 부분만 교체하므로 HMR 속도가 매우 빠르다
-
단점: 오래된 브라우저는 네이티브 ESM을 지원하지 않아 호환성 작업이 필요할 수 있음
- 그러나 Vite는 프로덕션 빌드 시에는 Rollup이라는 번들러를 사용해 이 문제 해결
번들러의 핵심 원리
번들러는 여러 개의 JS 파일을 분석해서 하나의 (또는 몇 개의) 파일로 합쳐주는 도구다. 이를 통해 네트워크 요청 횟수를 획기적으로 줄여 웹사이트 로딩 속도를 향상시킨다.
그렇다면 번들러는 어떻게 수많은 코드 파일을 '똑똑하게' 합칠 수 있을까?
추상 구문 트리(AST) - 코드를 이해하는 방법
AST
코드를 분석해서 컴퓨터가 이해할 수 있는 나무(Tree) 구조로 만든 것
번들러는 코드를 텍스트 그 자체로 읽는 게 아니라, AST로 변환해서 구조적으로 이해한다.
AST 생성 과정
크게 두 단계로 나뉜다.
-
어휘 분석 (Tokenizing)
- 코드를 토큰으로 쪼개는 과정이다.
- 예를 들어,
const sum = 1 + 2;라는 코드는 다음과 같은 토큰들로 분리된다.const(키워드)sum(변수명)=(연산자)1(숫자)+(연산자)2(숫자);(구분자)
-
구문 분석 (Parsing)
- 쪼개진 토큰들을 바탕으로, 문법 규칙에 따라 서로 어떤 관계를 가지는지 분석해서 트리 구조를 만든다.
const sum = 1 + 2;는 다음과 같은 구조로 표현될 수 있다.- 최상위 노드:
variableDeclaration(변수 선언)- 자식 노드 1:
Identifier(변수명:sum) - 자식 노드 2:
BinaryExpression(이항 표현식:1 + 2)- 자식 노드 2-1:
Literal(값:1) - 자식 노드 2-2:
Operator(값:+) - 자식 노드 2-3:
Literal(값:2)
- 자식 노드 2-1:
- 자식 노드 1:
- 최상위 노드:
AST가 왜 중요한가?
- 번들러가 코드를 조작하고 분석할 수 있게 해준다.
- 텍스트 상태로는 어려웠던 일들이 AST 구조에서는 가능해진다.
- 코드 변환: Babel이 최신 JS 문법을 구형 브라우저에서도 동작하는 코드로 바꾸는 것
- 코드 분석: 어떤 변수가 어디서 사용됐는지, 어떤 함수가 호출됐는지 추적
- 코드 최적화: 사용하지 않는 코드를 찾아내 제거하는 트리 셰이킹
트리 셰이킹(Tree Shaking) - 불필요한 코드 털어내기
Tree Shaking
나무를 흔들어서 죽은 잎사귀를 떨어뜨리는 것처럼, 실제로 사용되지 않는 코드(dead code)를 최종 번들 파일에서 제거하는 최적화 기술
트리 셰이킹의 원리
- 의존성 그래프 생성: 번들러는 먼저 프로젝트의 시작점(보통
index.js/main.js)부터 시작해서 모든import와export관계를 추적한다. 이 과정을 통해 어떤 파일이 어떤 파일에 의존하는지 나타내는 의존성 그래프를 만든다. - 필요한 코드 표시하기: 그래프를 따라가면서, 실제로
import되어 사용되는 함수, 클래스, 변수만 살아있는 코드로 표시한다. - 살아있지 않은 모든 코드는 최종 번들 파일에 포함시키지 않고 제거한다.
전체 프로세스 요약
번들러의 핵심 원리인 AST와 Tree Shaking을 알았으니, 이제 전체 프로세스를 요약하면 다음과 같다.
- 시작: 번들러가 프로젝트의 시작점부터 코드를 읽음
- 파싱: 각 파일을 AST로 변환하여 코드의 구조를 파악
- 그래프 생성: AST를 분석하여
import,export관계를 찾아내고, 전체 프로젝트의 의존성 그래프를 만든다. - 트리 셰이킹: 의존성 그래프를 바탕으로, 실제로 사용되는 코드만 골라낸다. 사용되지 않는 코드는 버려진다.
- 번들링: 살아남은 코드들(최적화된 AST)만 모아서 하나의 파일로 합친다. 이 과정에서 코드를 압축하고, 오래된 브라우저를 위해 문법을 변환하는 등의 추가적인 최적화도 수행한다.
- 결과: 최적화된 최종 번들 파일이 생성된다.