사전 번들러
개념

번들러

gabury1고친 사람 github-actions[bot]

번들러는 여러 파일로 나눠 쓴 자바스크립트 코드를 브라우저에 보낼 파일 몇 개로 묶어 주는 도구입니다. 브라우저가 파일 수백 개를 하나씩 받아 오던 일을 몇 번의 요청으로 줄입니다. 묶을 때 안 쓰는 코드를 걷어내고 파일 크기도 줄입니다. 프론트엔드 코드를 배포하기 전에 거치는 단계입니다.

쉽고 빠른 이해

번들러는 흩어진 자바스크립트 파일을 배포용 파일 몇 개로 묶습니다. main.js 가 math.js 의 add 를 가져다 쓰면, 번들러는 두 파일을 합쳐 bundle.js 하나를 만듭니다.

이게 없으면 브라우저가 파일을 하나 받을 때마다 서버에 다시 요청해야 합니다. 받은 파일을 열어 봐야 다음 파일을 알 수 있어서 파일이 많을수록 화면이 늦게 뜹니다. 라이브러리는 코드에 경로 없이 이름만 적어 부릅니다. 브라우저는 그 이름만 보고는 어느 파일인지 찾지 못합니다.

어떻게 도나:

  1. 진입점 파일 하나에서 출발합니다. 그 파일이 가져다 쓰는 파일을 줄줄이 따라가 목록을 만듭니다
  2. TypeScript 처럼 브라우저가 못 읽는 문법으로 쓴 파일은 자바스크립트로 바꿉니다. 안 쓰는 코드는 뺍니다
  3. 전부를 한 파일에 이어 붙여 내보냅니다

대가도 있습니다. 코드를 고칠 때마다 다시 묶어야 해서 빌드 단계가 하나 늘어납니다. 브라우저에서 도는 코드가 내가 쓴 코드와 모양이 달라서, 오류가 난 줄을 되짚으려면 원래 줄과 짝지은 파일이 따로 있어야 합니다. 스크립트 파일이 몇 개뿐인 작은 페이지라면 이 대가가 더 커서, 번들러 없이 파일을 그대로 보내기도 합니다.

상세

여행 가방을 싸는 일을 떠올리면 됩니다. 집 곳곳에 흩어진 물건 가운데 이번 여행에 쓸 것만 골라 가방 하나에 담습니다. 여행지에서는 가방 하나만 열면 필요한 물건이 다 나옵니다.

이 절은 자바스크립트 파일 두 개가 배포용 파일 하나가 되는 과정을 따라갑니다. 먼저 코드를 왜 여러 파일로 나눠 쓰는지, 나눈 파일을 따로 보내면 무엇이 곤란한지 봅니다. 이어서 번들러가 파일을 따라가며 묶는 순서를 보고, 묶으면서 함께 하는 일과 그 대가를 짚습니다.

모듈로 나눈 코드

자바스크립트 코드도 규모가 커지면 역할별로 파일을 나눕니다. 이렇게 나뉜 파일 하나를 모듈이라고 합니다. 백엔드에서 클래스를 파일별로 나누는 것과 같은 이유입니다. 한 파일이 한 가지 일만 맡아야 고치기 쉽습니다.

모듈끼리는 export 와 import 로 이어집니다. export 는 이 파일 밖에서 써도 되는 것을 내놓는 표시입니다. import 는 다른 파일이 내놓은 것을 가져오는 문장입니다. 아래에서 main.js 는 math.js 가 내놓은 add 를 가져다 씁니다.

JavaScript
// math.js
export function add(a, b) {
  return a + b;
}

// main.js
import { add } from './math.js';
console.log(add(2, 3));  // 5

main.js 에는 add 의 본문이 없습니다. 코드는 두 파일에 나뉘어 있지만, 실행할 때는 두 파일이 함께 있어야 5 가 나옵니다.

파일을 따로 보낼 때 생기는 일

브라우저는 스크립트 파일을 서버에서 하나씩 내려받습니다. 파일 하나마다 HTTP(HyperText Transfer Protocol) 요청이 한 번 오갑니다. 파일이 수백 개면 요청도 수백 번입니다.

더 곤란한 것은 순서입니다. 브라우저는 main.js 를 받아 열어 본 뒤에야 math.js 가 필요하다는 것을 압니다. math.js 가 또 다른 파일을 부르면 그 파일은 그다음에 받습니다. 부르는 사슬이 다섯 단계면 네트워크를 다섯 번 차례로 기다립니다.

라이브러리도 걸립니다. 프로젝트는 패키지 매니저로 남이 만든 라이브러리를 받아 node_modules 폴더에 쌓아 둡니다. 코드에는 import { formatDate } from 'date-lib' 처럼 경로 없이 이름만 적습니다. date-lib 는 설명을 위해 지은 이름입니다.

Node.js 는 서버에서 자바스크립트를 돌리는 실행 환경입니다. Node.js 는 이름만 적힌 모듈을 node_modules 폴더에서 찾습니다. 패키지 매니저가 라이브러리를 그 폴더에 쌓는 것도 이 때문입니다. 브라우저는 이 규칙을 모르므로, 따로 알려 주지 않으면 date-lib 가 어느 파일인지 찾지 못합니다.

진입점과 의존성 그래프

번들러는 앞의 세 문제를 배포 전에 미리 풉니다. 브라우저가 실행하면서 차례로 기다리던 따라가기를 번들러가 빌드할 때 대신 해 둡니다. 라이브러리 이름도 이때 node_modules 안의 파일로 바꿔 둡니다. 요청 수는 파일을 하나로 이으면서 줄어듭니다(아래 「한 파일로 잇기」).

따라가기의 출발점을 진입점이라고 합니다. 앱이 처음 실행하는 파일입니다. 위 예에서는 main.js 입니다.

번들러는 진입점을 읽어 import 문을 찾습니다. 그 문장이 가리키는 파일을 열어 또 import 를 찾습니다. 더 따라갈 import 가 없을 때까지 되풀이합니다.

한 파일이 돌려면 다른 파일이 있어야 하는 관계를 의존성이라고 합니다. 위 예에서 main.js 는 math.js 에 의존합니다.

번들러가 따라가며 모은 의존성을 점과 화살표로 이은 것이 의존성 그래프입니다. 예를 조금 넓혀 봅니다. main.js 가 math.js 와 format.js 를 부르고, format.js 가 node_modules 의 date-lib 를 부른다면 그래프는 이렇습니다.

flowchart TD
    M["main.js · 진입점"] --> A["math.js"]
    M --> F["format.js"]
    F --> P["date-lib · node_modules 안의 라이브러리"]

화살표는 부르는 쪽에서 불리는 쪽으로 갑니다. 프로젝트 폴더에 있어도 어디서도 부르지 않는 파일은 그래프에 없습니다. 그래서 번들에 들어가지 않습니다.

한 파일로 잇기

그래프가 완성되면 번들러는 그래프 안의 모듈을 한 파일에 이어 붙입니다. 이 결과 파일을 번들이라고 부릅니다. 번들러라는 이름도 여기서 왔습니다.

그냥 이어 붙이면 이름이 부딪힙니다. 두 파일이 각자 init 이라는 함수를 가졌다면, 한 파일 안에서는 뒤의 것이 앞의 것을 덮습니다.

번들러는 이것을 두 가지 방법으로 막습니다. 하나는 부딪히는 이름을 다른 이름으로 바꾸는 것입니다. 다른 하나는 모듈마다 코드를 함수 하나로 감싸는 것입니다. 함수 안에서 만든 이름은 함수 밖에서 보이지 않아서, 두 모듈의 init 이 서로를 덮지 않습니다.

import 와 export 도 번들 안에서는 파일을 받아 오는 문장이 아니게 됩니다. 같은 파일 안의 함수를 부르는 코드로 바뀝니다. 앞의 두 파일을 묶은 결과를 줄여 보이면 이런 모양입니다.

JavaScript
// bundle.js
function add(a, b) {
  return a + b;
}
console.log(add(2, 3));  // 5

import 줄이 사라지고 add 가 같은 파일 안으로 들어왔습니다. 이제 HTML(HyperText Markup Language) 파일은 <script> 태그 하나로 bundle.js 만 부르면 됩니다. 이 예에서 브라우저는 요청 한 번으로 앱 코드를 다 받습니다.

묶으면서 함께 하는 일

번들러는 그래프 안의 파일을 전부 한 번씩 읽습니다. 읽으면서 코드를 손보기도 합니다. 흔히 함께 하는 일은 넷입니다. 아래 표가 각각 무엇을 왜 하는지 보입니다.

일 무엇을 하나 왜 하나
트랜스파일 TypeScript 처럼 브라우저가 못 읽는 문법을 보통 자바스크립트로 옮긴다 브라우저는 자바스크립트만 실행한다
트리 셰이킹 그래프를 보고 어디서도 안 부르는 export 를 뺀다 라이브러리에서 함수 하나만 써도 나머지가 전부 따라 들어오는 것을 덜어 낸다
미니파이 공백과 주석을 지우고 긴 변수 이름을 짧게 바꾼다 내려받을 바이트 수를 줄인다
소스맵 번들의 각 줄이 원래 어느 파일 몇째 줄인지 짝지은 파일을 따로 낸다 브라우저에서 난 오류를 원래 코드에서 찾게 한다

앞의 셋은 결과 파일의 모양을 바꾸지만 코드가 하는 일은 바꾸지 않습니다. 소스맵은 번들 옆에 짝지은 파일을 하나 더 냅니다. 배포되는 번들은 사람이 읽기 어려워집니다. 사람이 읽고 고치는 코드는 소스 저장소에 남습니다.

코드 분할

모든 모듈을 하나로 묶으면 번들 하나가 커집니다. 첫 화면에 필요 없는 관리자 페이지 코드까지 처음에 다 받아야 합니다. 앱이 클수록 첫 화면이 늦게 뜹니다.

그래서 번들러는 번들을 몇 조각으로 다시 나눌 수 있습니다. 이것을 코드 분할이라고 합니다.

나뉜 조각 하나는 청크라고 부릅니다. 첫 화면에 필요한 청크만 먼저 보냅니다. 나머지는 그 화면으로 넘어갈 때 받습니다.

어디서 나눌지는 코드에 적습니다. import() 처럼 함수 꼴로 부른 모듈을 번들러는 나중에 받아도 된다는 표시로 읽습니다. 나누더라도 조각 수는 번들러가 그래프를 보고 몇 개로 정하므로, 파일 수백 개를 따로 받던 처음 상태로 돌아가지는 않습니다.

파일 이름에 붙는 해시

번들러는 결과 파일 이름에 파일 내용으로 계산한 해시 값을 붙이기도 합니다. main.3f9a1c.js 처럼 생긴 이름입니다. 가운데 문자열이 해시입니다.

코드가 한 글자라도 바뀌면 이 값이 바뀝니다. 그러면 파일 이름도 바뀝니다.

이렇게 하는 이유는 브라우저 캐시입니다. 브라우저는 한 번 받은 파일을 저장해 두고 같은 이름이면 다시 받지 않습니다. 새로 배포한 파일은 이름이 달라서 브라우저가 새로 받아 옵니다. 안 바뀐 파일은 이름도 같아서 저장해 둔 것을 계속 씁니다.

빌드 단계가 늘어나는 대가

번들러를 쓰면 코드를 고친 뒤 바로 브라우저에서 볼 수 없습니다. 다시 묶는 단계를 거쳐야 합니다. 파일이 많을수록 이 단계가 길어집니다.

그래서 개발 중에는 개발 서버를 함께 띄웁니다. 개발 서버는 개발하는 동안 내 컴퓨터에 띄우는 웹 서버로, 브라우저에 코드를 내줍니다. 파일을 저장하면 개발 서버가 바뀐 모듈만 다시 묶어 화면에 반영합니다.

페이지를 새로고침하지 않고 바뀐 모듈만 갈아 끼우는 기능을 핫 모듈 교체라고 합니다. 새로고침하지 않으니 화면에 입력해 둔 값이 그대로 남습니다.

설정도 대가입니다. 어느 파일을 진입점으로 삼을지, 어떤 변환을 거칠지를 번들러 설정 파일에 적습니다. 프로젝트가 커지면 이 설정도 함께 커집니다.

자바의 fat JAR 와 견주기

자바 백엔드에도 비슷한 일을 하는 도구가 있습니다. 애플리케이션 코드와 의존 라이브러리를 JAR(Java ARchive, 자바 아카이브) 파일 하나에 몰아넣은 것을 fat JAR라고 합니다. 서버에 파일 하나만 올리면 돌아가게 하려는 것입니다.

fat JAR 는 배포할 파일을 하나로 모으는 데서 일이 끝납니다. 번들러는 거기에 더해 브라우저가 네트워크로 받을 요청 수와 바이트 수를 줄여야 합니다. 트리 셰이킹과 미니파이를 번들러가 함께 맡는 것도 그래서입니다.

번들러라는 이름의 범위

번들러는 도구 하나의 이름이 아니라 이 일을 하는 도구들을 묶어 부르는 이름입니다. webpack · Rollup · esbuild · Parcel 이 번들러입니다.

빌드 도구는 번들러보다 넓은 이름입니다. 개발 서버와 번들링을 함께 맡습니다. 배포용 파일을 만들 때는 안에서 번들러를 부릅니다. Vite 가 이런 빌드 도구입니다.

관련 항목

번들러로 구현한 도구

webpack · Rollup · esbuild · Parcel · Vite · Browserify · Turbopack · Rspack

번들러가 읽고 따라가는 모듈 체계

모듈 · ES 모듈 · CommonJS · 진입점 · 의존성 그래프 · 동적 import · 순환 의존성

번들러가 묶으면서 함께 거치는 처리 단계

트랜스파일 · 트리 셰이킹 · 미니파이 · 소스맵 · 코드 분할 · 청크 · 스코프 호이스팅 · 데드 코드 제거

번들을 브라우저까지 나르는 경로

HTTP · HTTP-2 · CDN · 브라우저 캐시 · 캐시 무효화 · 콘텐츠 해시

번들러와 함께 도는 개발 도구

패키지 매니저 · npm · 트랜스파일러 · Babel · TypeScript · 개발 서버 · 핫 모듈 교체

번들을 실행하는 환경

브라우저 · JavaScript · 자바스크립트 엔진 · Node.js

번들러가 속하는 상위 분류

프론트엔드 · 툴체인 · 빌드 · 빌드 도구

다른 언어에서 코드를 한 파일로 묶는 방식

링커 · 정적 링크 · JAR · fat JAR

다른 이름: bundler · module bundler · 모듈 번들러 · 자바스크립트 번들러