코드 압축이 실제로 하는 일과 압축률의 한계
미니파이는 코드의 동작을 유지하면서 전송 바이트를 줄이는 변환입니다. 공백과 주석을 지우는 것에서 시작해, 지역 변수 이름을 한 글자로 바꾸고, 도달할 수 없는 코드를 제거하는 단계까지 갑니다. 다만 실제 전송량을 결정하는 것은 미니파이 단독이 아니라 미니파이와 gzip/brotli의 조합이며, 이 관계를 알아 두면 최적화 노력을 어디에 쓸지 판단하기 쉬워집니다.
미니파이가 수행하는 변환
식별자 축약이 지역 스코프에만 적용된다는 점이 중요합니다. 전역으로 노출되는 이름이나 객체 속성 이름은 외부에서 참조될 수 있으므로 기본적으로 바꾸지 않습니다. 그래서 속성 이름이 긴 코드는 미니파이 후에도 크기가 크게 줄지 않습니다.
| 단계 | 내용 | 효과 |
|---|---|---|
| 공백 제거 | 들여쓰기, 줄바꿈, 불필요한 공백 삭제 | 가장 큰 단일 감소분 |
| 주석 제거 | // 와 /* */ 삭제 (라이선스 주석은 보존 옵션) | 주석이 많은 코드에서 큼 |
| 식별자 축약 | 지역 변수·함수 인자를 a, b, c 로 | 함수가 많을수록 효과적 |
| 구문 축약 | true → !0, if/else → 삼항 연산자 | 누적하면 무시할 수 없음 |
| 죽은 코드 제거 | 도달 불가 분기, 사용되지 않는 변수 삭제 | 조건부 코드가 많을 때 |
| 상수 접기 | 60 * 60 * 1000 → 3600000 | 미미하지만 무료 |
사용 방법
- 압축할 코드를 입력합니다.
- 결과와 원본의 크기 비교를 확인합니다.
- 압축된 코드를 배포 대상에 사용합니다. 원본은 반드시 따로 보관하세요.
- 실제 서비스라면 이 도구로 수동 압축하기보다 빌드 도구에 맡기는 것이 맞습니다. 아래 항목을 참고하세요.
미니파이와 gzip의 관계
gzip과 brotli는 반복되는 패턴을 사전으로 치환해 압축합니다. 그런데 미니파이가 제거하는 것들, 특히 들여쓰기 공백과 줄바꿈은 대표적인 반복 패턴이라 gzip이 매우 효율적으로 처리합니다. 그래서 '미니파이로 40% 줄었다'는 수치는 압축 전 기준일 뿐, gzip을 적용한 뒤 비교하면 차이가 훨씬 작아집니다.
그럼에도 미니파이가 의미 있는 이유는 두 가지입니다. 첫째, 식별자 축약과 죽은 코드 제거는 gzip이 대신할 수 없는 감소입니다. 둘째, 브라우저는 전송된 바이트가 아니라 압축을 푼 코드를 파싱하고 실행하므로, 압축 해제 후 크기가 작아지면 파싱·컴파일 시간이 줄어듭니다. 저사양 기기에서는 이 파싱 비용이 다운로드 시간보다 크게 체감되는 경우도 있습니다.
정리하면 순서는 미니파이 후 압축입니다. 둘은 대체 관계가 아니라 보완 관계입니다.
빌드 도구에 맡겨야 하는 이유
수동 미니파이의 결정적 약점은 소스맵이 없다는 것입니다. 소스맵이 없으면 프로덕션 오류의 스택 트레이스가 a.b is not a function 같은 형태로 나타나 원인 파악이 사실상 불가능해집니다. 빌드 도구는 미니파이와 동시에 소스맵을 생성하므로 오류 추적 서비스에서 원본 코드 위치를 볼 수 있습니다.
또 번들러는 미니파이보다 효과가 큰 최적화를 함께 수행합니다. 트리 셰이킹으로 사용하지 않는 모듈을 아예 제외하고, 코드 스플리팅으로 초기 로드에 필요한 부분만 내려보냅니다. 큰 라이브러리에서 함수 하나만 쓰는 경우 트리 셰이킹의 감소폭이 미니파이보다 훨씬 큽니다.
- JavaScript: esbuild, Terser, SWC (Vite·Next.js 등에 내장)
- CSS: Lightning CSS, cssnano (PostCSS 플러그인)
- HTML: html-minifier-terser
- 이미지·폰트: 미니파이 대상이 아니며 별도의 최적화 경로를 씁니다
미니파이가 코드를 깨뜨리는 경우
- 함수 이름에 의존하는 코드: fn.name 을 읽거나 클래스 이름으로 분기하는 로직은 식별자가 바뀌면 동작이 달라집니다. 일부 DI 컨테이너와 직렬화 라이브러리가 여기에 해당합니다.
- 문자열로 참조하는 속성: obj["someProperty"] 와 obj.someProperty 를 섞어 쓰면서 속성 축약을 켜면 불일치가 생깁니다.
- eval 이나 with: 스코프를 정적으로 분석할 수 없으므로 안전하게 축약할 수 없습니다.
- 세미콜론 생략 코드: 줄바꿈이 사라지면서 두 문장이 하나로 합쳐지는 경우가 있습니다. ASI 규칙에 의존하는 코드는 특히 주의가 필요합니다.
- 라이선스 주석 소실: MIT나 Apache 라이선스는 고지 유지를 요구합니다. /*! 로 시작하는 주석을 보존하는 옵션을 켜세요.
- 부수 효과가 있는 import: 사용하지 않는 것처럼 보여 제거되지만 실제로는 폴리필처럼 동작하는 모듈이 있습니다. package.json의 sideEffects 설정으로 알려 주어야 합니다.
압축률을 좌우하는 요인
같은 도구를 써도 코드에 따라 감소폭이 크게 다릅니다. 주석과 들여쓰기가 많고 지역 변수 이름이 긴 코드는 절반 이하로 줄기도 합니다. 반대로 이미 압축된 코드, 긴 문자열 리터럴이 대부분인 코드, 데이터가 코드 안에 하드코딩된 파일은 거의 줄지 않습니다. 미니파이 결과가 실망스럽다면 파일 내용의 상당 부분이 문자열이나 데이터인지 확인해 보세요. 그 경우 해결책은 미니파이가 아니라 데이터를 별도 파일로 분리해 필요할 때 불러오는 것입니다.
자주 묻는 질문
- 압축된 코드를 원래대로 되돌릴 수 있나요?
- 완전히는 불가능합니다. 포매터로 들여쓰기를 복원하면 읽을 수는 있지만, 삭제된 주석과 원래 변수 이름은 되살릴 수 없습니다. 소스맵이 함께 배포되어 있다면 원본을 복원할 수 있는데, 이는 곧 프로덕션에 소스맵을 공개하면 소스가 공개된다는 뜻이기도 합니다. 오류 추적 서비스에만 업로드하고 공개 경로에는 두지 않는 것이 일반적인 운영 방식입니다.
- 미니파이가 코드를 난독화하나요?
- 결과적으로 읽기 어려워지지만 난독화가 목적은 아닙니다. 미니파이는 크기 감소를 위한 것이고, 난독화는 분석을 방해하기 위해 제어 흐름을 일부러 복잡하게 만드는 별개의 처리입니다. 난독화는 코드 크기를 늘리고 실행 속도를 떨어뜨립니다. 그리고 어느 쪽도 클라이언트 코드에 담긴 비밀을 보호하지 못합니다. API 키를 프론트엔드에 두면 어떤 처리를 하더라도 노출됩니다.
- CSS도 같은 방식으로 압축되나요?
- 기본 아이디어는 같지만 CSS 전용 최적화가 더 있습니다. 색상 표기 축약(#ffffff → #fff), 단위 정리(0px → 0), 중복 선택자 병합, 사용되지 않는 접두사 제거 등입니다. 다만 선택자 병합은 CSS의 우선순위와 선언 순서에 영향을 줄 수 있어, 공격적인 옵션을 켰다면 시각적 회귀를 확인해야 합니다.
- 얼마나 줄어드는 것이 정상인가요?
- 일반적인 JavaScript는 미니파이만으로 30~50%, gzip까지 적용하면 원본의 25~30% 정도가 되는 경우가 많습니다. 다만 앞서 설명한 대로 코드 성격에 따라 편차가 큽니다. 절대적인 기준보다 배포마다 번들 크기를 기록해 추이를 보는 편이 실용적입니다.
- 입력한 코드가 서버로 전송되나요?
- 전송되지 않습니다. 브라우저에서 처리합니다. 다만 비공개 저장소의 소스 코드를 외부 사이트에 붙여넣는 것은 조직 정책상 문제가 될 수 있으니 확인 후 사용하세요.
- HTTP/2를 쓰면 미니파이가 덜 중요해지나요?
- HTTP/2가 개선한 것은 요청 수에 따른 병목이지 전송량이 아닙니다. 여러 파일을 병렬로 받는 비용이 낮아졌으므로 번들을 하나로 합칠 필요는 줄었지만, 바이트 수를 줄이는 미니파이의 가치는 그대로입니다. 파싱 비용 감소 효과도 프로토콜과 무관합니다.
💡 참고: 압축된 코드를 배포할 때는 반드시 소스맵을 함께 생성해 오류 추적 서비스에 업로드하세요. 소스맵 없는 프로덕션 스택 트레이스는 사실상 읽을 수 없습니다.