사전 미니파이
개념

미니파이

gabury1고친 사람 github-actions[bot]

미니파이는 코드가 하는 일은 바꾸지 않고 파일 크기만 줄입니다. 사람이 읽기 좋으라고 넣은 빈칸과 주석을 걷어 냅니다. 작아진 파일은 브라우저가 더 빨리 내려받습니다.

쉽고 빠른 이해

미니파이는 「배포할 코드를 기계만 읽으면 되게 줄여 쓰기」입니다. 여러 줄짜리 함수가 빈칸 없는 한 줄로 바뀝니다. price 같은 변수 이름은 n 한 글자가 됩니다.

웹 앱의 코드는 사용자가 페이지를 열 때마다 네트워크를 건너 브라우저로 갑니다. 들여쓰기와 주석도 그때 같이 실려 갑니다. 브라우저는 그것들 없이도 코드를 똑같이 실행합니다. 그런데도 실려 가는 글자가 많을수록 내려받는 시간은 길어집니다.

도는 순서는 이렇습니다.

  1. 빌드할 때 도구가 코드를 읽어 문법 구조를 파악합니다
  2. 그 구조 위에서 안에서만 쓰는 이름을 짧게 바꿉니다
  3. 같은 뜻의 더 짧은 표기가 있으면 그것으로 바꿔 적습니다
  4. 구조를 다시 코드로 적습니다. 이때 빈칸과 주석은 넣지 않습니다

결과물은 사람이 읽기 어렵다는 대가가 있습니다. 그래서 오류가 나면 원래 코드의 몇 번째 줄인지 알려 주는 파일을 따로 만들어 둡니다.

미니파이는 사용자에게 내보내는 배포용 빌드에서 켭니다. 코드를 자주 고치는 개발 중에는 끕니다.

상세

미니파이가 왜 필요한지부터 서버 코드와 견주어 봅니다. 그다음 짧은 함수 하나가 줄어드는 모습을 보면서 줄이는 방법 세 가지를 하나씩 짚습니다.

뒤로는 이름을 줄일 때 무엇을 조심하는지, 줄인 코드에서 오류를 어떻게 되짚는지 봅니다. 끝으로 압축과 어떻게 일을 나누는지, 언제 쓰고 언제 안 쓰는지를 정리합니다.

전보를 떠올려 보겠습니다. 전보는 글자 수대로 요금을 냈습니다. 그래서 「무사히 도착했으니 걱정하지 마세요」 대신 「무사도착 걱정마라」라고 쳤습니다. 받는 사람은 그 짧은 글에서 뜻을 다 알아들었습니다.

미니파이도 같은 일을 합니다. 코드의 뜻은 그대로 두고 글자 수만 줄입니다. 웹에서는 글자 수가 곧 내려받는 시간이기 때문입니다.

코드가 네트워크를 건너는 문제

서버 애플리케이션의 코드는 한 번 배포하면 서버 안에 머뭅니다. 소스에 주석이 많든 들여쓰기가 깊든 사용자와는 상관이 없습니다. 사용자가 받는 것은 실행 결과뿐입니다.

웹 앱은 다릅니다. 브라우저에서 도는 자바스크립트와 CSS(Cascading Style Sheets, 화면 모양을 정하는 언어)는 사용자가 페이지를 열 때마다 서버에서 내려갑니다. 파일이 클수록 내려받는 시간이 길어집니다. 그동안 화면은 기다립니다. 모바일 회선처럼 대역폭이 좁으면 그 차이가 더 벌어집니다.

그런데 소스 파일에는 사람을 위한 글자가 많습니다. 들여쓰기, 줄바꿈, 주석, 뜻이 드러나게 길게 지은 이름이 그렇습니다. 브라우저는 이것들이 없어도 코드를 똑같이 실행합니다. 미니파이는 이 글자들을 배포 전에 걷어 냅니다.

줄이는 세 가지 방법

세금을 붙이는 짧은 함수 하나로 따라가 보겠습니다. 사람이 쓴 원래 코드는 이렇습니다.

JavaScript
// 세금을 붙인 가격을 돌려준다
function addTax(price) {
  const rate = 0.1;
  return price + price * rate;
}

미니파이를 거치면 아래처럼 한 줄이 됩니다. 같은 값을 넣으면 두 함수가 돌려주는 값도 같습니다.

JavaScript
function addTax(n){return n+n*.1}

두 코드 사이에서 바뀐 것을 세 갈래로 나누면 아래 표와 같습니다.

방법 바꾸는 것 위 예에서
빈칸과 주석 지우기 들여쓰기 · 줄바꿈 · 주석 주석 한 줄과 들여쓰기가 사라짐
이름 줄이기 함수 안에서만 쓰는 변수 이름 price 가 n 이 됨
짧은 표기로 바꾸기 같은 뜻의 더 긴 표기 rate 가 값으로 바뀌고 0.1 이 .1 이 됨

첫째가 가장 단순합니다. 빈칸과 줄바꿈은 낱말과 기호를 서로 가르는 데만 쓰입니다. 그래서 꼭 필요한 빈칸만 남기고 지워도 뜻이 안 변합니다. function addTax 사이처럼 붙이면 다른 낱말이 되는 곳만 한 칸 남습니다.

둘째는 이름입니다. price 는 함수 바깥에서 아무도 부르지 않는 이름입니다. 함수 안에서 이 이름을 빠짐없이 바꾸면 동작이 같습니다. 이렇게 이름을 짧게 바꾸는 일을 따로 맹글링(mangling)이라고 부릅니다.

셋째는 같은 뜻의 짧은 표기입니다. 한 번만 쓰이는 상수 rate 는 그 자리에 값 0.1 을 바로 적습니다. 그러면 const rate = 0.1; 선언은 쓸 데가 없어져 지웁니다.

값을 적는 표기 자체도 더 짧게 바꿉니다. 흔히 쓰는 바꿔 적기는 아래와 같습니다. 오른쪽 주석이 바뀐 결과입니다.

JavaScript
0.1        // .1
true       // !0
false      // !1

!0 과 !1 은 자바스크립트가 숫자를 참거짓으로 읽는 규칙에 기댑니다. 자바스크립트는 숫자 0 을 거짓으로, 0 이 아닌 숫자를 참으로 읽습니다. ! 는 참과 거짓을 뒤집는 연산자입니다. 그래서 !0 은 참, !1 은 거짓이 됩니다.

글자 수는 줄어도 값은 같습니다. CSS 에도 같은 생각을 씁니다. 흰색 #ffffff 는 #fff 로 적습니다. 0px 는 0 으로 적습니다.

코드를 읽고 다시 적는 도구

이 세 가지는 글자를 찾아 바꾸는 방식으로는 못 합니다. 주석처럼 보이는 // 가 문자열 안에 들어 있을 수 있습니다. 같은 price 라도 어느 함수의 이름인지 알아야 바꿀 수 있습니다.

미니파이 도구는 먼저 파서로 코드를 읽습니다. 파서는 코드를 문법에 맞게 쪼개는 프로그램입니다. 글자를 하나씩 보는 대신 「여기부터 여기까지가 문자열」처럼 뜻의 단위를 가려 냅니다. 덕분에 문자열 안의 // 를 주석으로 잘못 보지 않습니다.

파서가 쪼갠 결과는 추상 구문 트리라는 구조로 남습니다. 추상 구문 트리는 「이 함수 안에 이 선언과 이 반환문이 있다」를 나무 모양으로 담은 구조입니다. 도구는 이 구조를 보고 어느 price 가 어느 함수의 것인지 압니다.

도구는 이 구조 위에서 이름을 바꾸고 표기를 줄입니다. 그다음 구조를 다시 코드로 적어 냅니다. 적을 때는 빈칸과 주석을 아예 넣지 않습니다. 첫째 방법인 빈칸 지우기는 사실상 이 마지막 단계에서 저절로 이루어집니다.

이름을 줄일 수 없는 경우

이름 줄이기는 그 이름을 부르는 곳을 전부 알 때만 안전합니다. 함수 안의 지역 변수는 그 함수 안에서만 보입니다. 이렇게 이름이 보이는 범위를 스코프라고 합니다. 범위가 좁으니 도구가 부르는 곳을 빠짐없이 찾을 수 있습니다.

바깥에 보이는 이름은 다릅니다. 다른 파일이 부르는 함수 이름이나 전역 변수가 그렇습니다. 이런 이름을 바꾸면 부르는 쪽은 옛 이름을 찾다가 실패합니다. 그래서 미니파이 도구는 대개 이런 이름을 기본으로 건드리지 않습니다. 위 예에서 addTax 가 남은 것도 그래서입니다.

객체의 속성 이름도 같은 이유로 남겨 둡니다. 서버가 보내는 JSON(JavaScript Object Notation, 자바스크립트 객체 표기법) 응답의 키가 price 라면 코드도 price 로 꺼내야 합니다. 코드 쪽 이름만 바뀌면 값을 못 찾습니다.

이름을 문자열로 다루는 코드도 조심해야 합니다. 함수 이름을 꺼내 로그에 찍는 코드라면 미니파이 뒤에는 n 이나 a 가 찍힙니다. 자바에서 클래스 이름을 문자열로 찾는 리플렉션 코드가 이름 변경에 약한 것과 같은 이치입니다.

줄인 코드에서 오류 되짚기

스택 트레이스는 오류가 난 곳까지 이어진 함수 호출을 줄 번호와 함께 보여 주는 기록입니다. 미니파이된 코드에서는 이 기록이 쓸모를 잃습니다. 파일 전체가 한 줄이라 「1번 줄의 4만 몇 번째 글자」 같은 위치만 나옵니다. 함수 이름도 a, n 이라 무엇인지 알 수 없습니다.

이 문제를 소스맵(source map)이 풉니다. 소스맵은 줄인 코드의 각 위치가 원래 코드의 어느 파일, 몇 번째 줄인지 적은 별도 파일입니다. 미니파이 도구가 결과물을 만들 때 이 파일도 만듭니다. 브라우저의 개발자 도구나 오류 수집 서비스가 이 파일을 읽으면 원래 줄 번호와 이름을 보여 줍니다.

소스맵을 누구나 받게 둘지는 팀이 정합니다. 소스맵을 받은 사람은 원래 파일 이름과 변수 이름까지 되살려 읽을 수 있기 때문입니다. 그래서 사용자에게는 내보내지 않고 오류 수집 서비스에만 올리는 방식도 흔합니다.

다만 소스맵을 감춰도 코드 자체가 숨겨지지는 않습니다. 브라우저가 실행하려면 미니파이된 코드는 어차피 사용자에게 내려가야 합니다. 줄을 다시 나눠 주는 포매터에 넣으면 이름은 짧아도 흐름은 따라 읽을 수 있습니다. 코드를 일부러 알아보기 어렵게 만드는 일은 난독화라고 따로 부릅니다.

함께 도는 압축과 트리 셰이킹

미니파이와 자주 헷갈리는 것이 HTTP(HyperText Transfer Protocol, 웹에서 요청과 응답을 주고받는 규약)로 파일을 보낼 때 거는 HTTP 압축입니다. 서버가 파일을 gzip 이나 Brotli 같은 방식으로 줄여 보내면 브라우저가 받아서 풉니다. 이 압축은 풀면 원래 파일이 글자 하나 다르지 않게 돌아옵니다.

미니파이는 되돌릴 수 없습니다. 지운 주석과 원래 이름은 결과물 어디에도 남지 않습니다. 대신 브라우저가 압축을 풀고 난 뒤에도 파일이 작습니다. 그만큼 브라우저가 코드를 읽어 들이는 일도 줄어듭니다.

둘은 맡는 단계가 달라서 함께 씁니다. 미니파이는 빌드 때 한 번 합니다. 압축은 파일을 보낼 때마다 합니다. 보통은 번들러가 여러 파일을 하나로 묶은 뒤에 미니파이를 겁니다. 한 파일이 브라우저에 닿기까지의 순서는 아래와 같습니다.

flowchart TD
    A["원래 코드 여러 파일"] --> B["번들러가 하나로 묶기"]
    B --> C["미니파이"]
    C --> D["배포 파일과 소스맵"]
    D --> E["서버가 압축해서 전송"]
    E --> F["브라우저가 풀고 실행"]

묶는 단계에서는 트리 셰이킹도 돕니다. 트리 셰이킹은 어디서도 안 쓰는 코드를 빼서 남길 코드를 고릅니다. 미니파이는 그렇게 남은 코드를 짧게 적습니다. 둘 다 내려받을 바이트를 줄이지만 줄이는 대상이 다릅니다.

쓰는 때와 안 쓰는 때

미니파이는 사용자에게 내보내는 배포용 빌드에 겁니다. 웹 프론트엔드의 번들러는 대개 배포용 빌드에서 미니파이를 기본으로 켭니다.

개발 중에는 끕니다. 코드를 고칠 때마다 다시 줄이면 빌드가 느려집니다. 브라우저에서 코드를 들여다볼 때도 원래 모양이 읽기 쉽습니다.

서버에서 도는 코드에는 얻는 것이 적습니다. Node.js 로 짠 백엔드 코드는 네트워크를 건너 사용자에게 가지 않기 때문입니다. 줄여도 아끼는 것이 디스크 몇 바이트뿐입니다.

쓰기로 했다면 치르는 대가는 셋입니다.

대가 생기는 일 줄이는 법
읽기 어려움 오류 위치와 이름을 알아볼 수 없다 소스맵을 함께 만든다
빌드 시간 파일이 클수록 줄이는 데 오래 걸린다 개발 빌드에서는 끈다
이름에 기대는 코드 바뀐 이름 탓에 동작이 달라진다 그 이름은 바꾸지 말라고 도구에 알린다

관련 항목

미니파이로 줄이는 언어와 파일

JavaScript · CSS · HTML · JSON · SVG

미니파이를 구현한 도구

Terser · UglifyJS · esbuild · SWC · cssnano · Lightning CSS · html-minifier

미니파이를 빌드에 끼워 돌리는 번들러

번들러 · webpack · Rollup · Vite · Parcel

미니파이와 함께 거치는 빌드 단계

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

미니파이가 속으로 쓰는 기법

파서 · 추상 구문 트리 · 맹글링 · 상수 폴딩 · 인라이닝 · 스코프

미니파이와 혼동되는 이웃 기법

HTTP 압축 · gzip · Brotli · 압축 · 난독화

미니파이로 줄이려는 비용

대역폭 · 지연 · 페이지 로드 · Core Web Vitals · 브라우저 캐시 · CDN

미니파이가 속하는 상위 분류

프론트엔드 · 빌드 · 웹 성능 · 프로덕션 빌드

다른 이름: minify · minification · minifier · 미니파이어 · 미니피케이션 · 코드 최소화 · 코드 경량화