Skip to content
블로그로 돌아가기
튜토리얼

코드 압축(minify) 완벽 가이드: CSS, JS, HTML

코드 압축이란 무엇이고 CSS, JS, HTML이 어떻게 압축되는지, minify와 gzip/brotli가 왜 다른지 설명합니다. 순서를 이해하고 온라인 minifier로 무료로 압축하세요.

15 분 소요

코드 압축(minify) 완벽 가이드: CSS, JS, HTML

코드 최소화(minify)는 CSS, JavaScript(자바스크립트), HTML 소스에서 기계가 필요로 하지 않는 문자(공백, 주석, 줄바꿈)를 제거하고, 장황한 표현을 더 짧은 등가 표현으로 다시 쓰는 작업입니다. 동작은 그대로 유지되고, 파일만 작아져서 더 빨리 로드됩니다.

시작하기 전에 한 가지를 분명히 해 둡시다. minify는 압축(compression)이 아닙니다. minify는 소스 코드를 대상으로 문법적 군더더기를 걷어냅니다. gzip과 brotli는 전송 중인 바이트를 대상으로 반복 패턴을 인코딩합니다. 둘은 서로 다른 단계에서 실행되고 서로 다른 종류의 군더더기를 처리하며, 효과가 쌓입니다. 그래서 서버가 이미 brotli로 서빙하더라도 minify는 여전히 해야 합니다. 이 가이드는 그 이유를 설명합니다.

지금 당장 압축하고 싶다면 CSS minifier, JavaScript minifier, HTML minifier로 바로 가세요. 각 도구는 전부 브라우저 안에서만 실행됩니다. 다만 원리를 이해해야 어느 계층에서 압축할지, 그리고 손으로 직접 압축할 필요가 있는지를 판단할 수 있습니다. minify가 실제로 하는 일, CSS·JS·HTML이 각각 어떻게 최소화되는지, minify가 gzip 및 brotli와 어떻게 함께 작동하는지, 언제 빌드 도구가 이미 알아서 처리해 주는지, source map이 최소화된 코드를 어떻게 디버깅 가능하게 유지하는지를 차례로 다룹니다.

최소화란 무엇인가 (그리고 무엇이 아닌가)

최소화는 두 가지 일을 합니다. 파서에게 아무 의미가 없는 문자를 삭제하고, 소스를 정확히 같은 의미의 더 짧은 형태로 다시 씁니다. 결과물은 기계에게는 완전히 등가이지만 사람에게는 거의 읽을 수 없습니다. 코드가 동작하는 방식은 전혀 바뀌지 않고, 겉모습만 바뀝니다.

이 마지막 지점은 가이드 전체에서 계속 기억해야 합니다. minify는 소스의 표면, 즉 공백·주석·식별자 이름·군더더기 문법만 손대고 동작이나 출력은 건드리지 않습니다. 이것은 포매팅과 정확히 반대입니다. 포매팅은 코드를 읽기 쉽게 만들려고 공백을 더하고, 최소화는 코드를 작게 만들려고 공백을 걷어냅니다. 둘 다 같은 “의미 등가” 축 위에 있고 방향만 반대입니다.

사람들은 비슷하게 들리는 세 가지 작업을 자주 혼동합니다. 다음 표로 정리합니다.

구분포맷(beautify)Minify압축(gzip/brotli)
무엇을 바꾸나공백, 줄바꿈, 들여쓰기 추가공백·주석 제거, 문법 단축반복 패턴의 바이트 단위 인코딩
어느 계층소스 코드소스 코드전송 / 저장
여전히 소스 코드인가예 (읽을 수 있음)예 (실행 가능, 읽기 어려움)아니오 (이진, 디코딩 필요)
누가 하나개발자 / 에디터빌드 도구 / minifier서버 + 브라우저
되돌릴 수 있나의미적으로의미적으로 (동작 불변)완전히 (압축 해제로 바이트 복원)

포맷과 minify는 한 축, 즉 의미 등가 축 위에 함께 있습니다. 압축은 완전히 다른 축 위에 있습니다. 포맷된 파일과 최소화된 파일은 둘 다 유효한 소스이지만, 압축된 파일은 무언가가 실행되기 전에 반드시 디코딩되어야 하는 이진 덩어리입니다.

여기서 비용이 큰 오해가 생깁니다. “내 서버는 이미 gzip을 하니까 minify는 의미가 없다”는 생각입니다. 그렇지 않으며, 가이드 뒤쪽의 수치가 그 이유를 보여줍니다. 최소화와 압축은 서로 다른 군더더기를 제거하므로, 한쪽을 한다고 해서 다른 쪽이 불필요해지지는 않습니다. 각 언어를 살펴보는 동안 이 점을 기억하세요.

minifier가 제거하는 바이트가 애초에 왜 존재하는지 생각해 보면 이해에 도움이 됩니다. 공백, 주석, 서술적인 이름은 여러분 자신과 동료를 위해 씁니다. 코드를 리뷰하고 유지보수하는 데 쓰입니다. 여러분의 CSS를 파싱하고 JavaScript를 실행하고 DOM을 구성하는 기계는 그것들을 모두 무시합니다. 최소화는 사람이 소스 작업을 마친 뒤에 사람만을 위한 자료를 버리는 단계입니다. 그래서 최소화는 개발이 아니라 프로덕션의 관심사입니다. 읽기 쉬운 버전은 저장소에 보관하고, 군더더기를 걷어낸 버전을 브라우저로 배포합니다. 읽기 쉬운 사본이 원본이고, 최소화된 사본은 언제든 다시 생성할 수 있는 빌드 산출물입니다.

CSS 최소화는 어떻게 작동하나

CSS는 셋 중 가장 다루기 쉬운 대상입니다. 문법에 모호함이 거의 없기 때문입니다. minifier는 주석을 걷어내고, 연속된 공백을 없애고, 각 블록의 마지막 세미콜론을 떨어뜨리고, {, }, :, ; 주위의 공백을 제거합니다. 이것만으로도 대부분의 바이트가 사라집니다.

CSS는 또한 다른 언어에는 없는 등가 재작성을 허용합니다. 좋은 minifier는 이를 안전하게 적용합니다.

  • 색상 단축. #ffffff#fff가 되고, #ff0000red로 줄어듭니다(또는 그 반대로, 쓰기에 더 짧은 쪽으로).
  • 0 값의 단위 제거. 0px0이 되고, margin: 0 0 0 0margin: 0이 됩니다.
  • 앞쪽 0 제거. 0.5em.5em이 됩니다.
  • 단축 속성 병합. margin-top, margin-right, margin-bottom, margin-left 네 개의 개별 선언이 하나의 margin으로 접힙니다.
  • 규칙 결합. 동일한 선택자나 선언을 가진 인접 규칙은 병합할 수 있고, 중복된 선언은 제거할 수 있습니다.

이 변환들은 모두 렌더링 결과를 동일하게 유지합니다. 그것이 규격을 지키는 minifier가 넘지 않는 경계입니다. 다만 CSS는 순서에 민감합니다. 나중에 나온 규칙이 캐스케이드를 통해 앞선 규칙을 덮어씁니다. 그래서 안전한 minifier는 어느 선언이 이기는지를 바꿀 수 있는 규칙을 함부로 재배치하지 않습니다. 바이트를 줄이는 것은 허용되지만 캐스케이드를 바꾸는 것은 허용되지 않습니다.

이 제약은 생각보다 미묘합니다. 병합할 수 있어 보이는 두 선언이 실제로는 병합되지 않을 수 있습니다. 그 사이에 있는 무언가가 같은 명시도로 같은 속성을 참조하기 때문입니다. 예를 들어 보겠습니다.

.btn { color: #ff0000; }
.alert .btn { color: blue; }
.btn { color: #f00; }

첫 번째와 세 번째 규칙은 선택자를 공유하므로 병합할 수 있습니다. 단, 그렇게 해도 두 규칙 모두에 매칭되는 요소에서 어느 쪽이 이기는지를 바꿀 만큼 선언이 가운데 규칙을 지나쳐 옮겨지지 않을 때에만 그렇습니다. 이들을 그냥 재배치해 병합하면 캐스케이드가 깨질 수 있습니다. CSSO 같은 프로덕션 등급 엔진은 바로 이런 엣지 케이스를 추론하도록 설계되었고, 정규식으로 “공백만 지우는” minifier를 직접 만들면 안 되는 이유도 여기에 있습니다. 변환은 기계적으로 보이지만 그 뒤의 안전성 분석은 그렇지 않습니다.

CSS minifier는 이런 무손실 최소화를 위해 CSSO 엔진을 사용하며, 전부 브라우저 안에서 실행되고 각 패스의 페이로드 영향을 볼 수 있도록 절약된 바이트를 표시합니다. 같은 도구가 반대 방향으로 포매팅도 하므로, 운영 중인 사이트에서 복사한 최소화된 스타일시트를 읽기 쉬운 들여쓰기 규칙으로 다시 펼칠 수 있습니다. CSS 조각을 복사해 압축된 크기를 확인하고 싶을 때, 또는 빌드 단계가 없는 정적 페이지를 배포할 때 쓰면 됩니다.

JavaScript 최소화는 어떻게 작동하나

JavaScript 최소화는 CSS보다 훨씬 더 나아가며, 절약 효과가 큰 만큼 함정도 함께 있습니다. 작은 함수를 Terser 적용 전후로 비교해 보겠습니다.

// 적용 전
function calculateTotal(items, taxRate) {
  let runningTotal = 0;
  for (const item of items) {
    runningTotal += item.price * item.quantity;
  }
  return runningTotal * (1 + taxRate);
}
// 적용 후
function calculateTotal(t,a){let n=0;for(const o of t)n+=o.price*o.quantity;return n*(1+a)}

함수 이름 calculateTotal은 살아남습니다. 내보내졌거나 다른 곳에서 호출될 수 있기 때문입니다. 매개변수와 루프 변수는 한 글자로 접힙니다. 그게 핵심이지만, JS minifier는 별개의 여러 일을 합니다.

  • 식별자 mangle. 지역 변수와 매개변수는 한 글자로 이름이 바뀝니다. getUserPreferencesa가 됩니다. 지역 변수만 mangle되며, 전역 이름과 내보낸 이름은 기본적으로 유지됩니다. 이들의 이름을 바꾸면 바깥에서 참조하는 코드가 깨지기 때문입니다.
  • 데드 코드 제거. 도달할 수 없는 분기와 사용되지 않는 변수가 제거되며, 번들러 수준의 tree-shaking과 맞물려 작동합니다.
  • 상수 폴딩과 문법 압축. 표현식이 짧아집니다. true!0이 되고, false!1이 되며, return undefined;return;이 됩니다.

JS 최소화에서 가장 알아 둘 만한 것은 자동 세미콜론 삽입(ASI) 함정입니다. JavaScript는 세미콜론을 생략할 수 있게 해 주고, 파서가 특정 규칙에 따라 대신 삽입합니다. minifier가 그 규칙이 의존하는 줄바꿈을 삭제하면 코드의 의미가 바뀔 수 있습니다. 전형적인 실패는 ([로 시작하는 문장이 슬그머니 앞 줄에 들러붙는 경우입니다.

const x = getValue()
[1, 2, 3].forEach(handle)

세미콜론이 없으면 이것은 두 개의 문장이 아니라 getValue()[1, 2, 3], 즉 인덱싱 표현식으로 파싱됩니다. 한 줄로 최소화되고 나면 그 버그가 굳어집니다. 같은 위험이 (로 시작하는 줄에서도 나타나는데, 이때는 앞의 표현식이 함수처럼 호출됩니다. 최신 Terser는 코드를 먼저 추상 구문 트리로 파싱한 뒤 필요한 곳에 세미콜론을 다시 내보내므로, 현장의 대부분 경우를 무리 없이 처리합니다. 무작정 텍스트를 삭제하는 게 아닙니다. 다만 나쁜 소스에 공격적인 최소화가 더해지면 실제 프로덕션 버그의 원천이 되고, 그 실패가 개발이 아니라 최소화된 빌드에서만 나타나기 때문에 더 까다롭습니다. 해결책은 여러분 쪽에 있습니다. 명시적인 세미콜론과 모호하지 않은 문법으로 코드를 작성하면 minifier는 안전하게 동작합니다. 소스 수준에서 세미콜론을 삽입해 주는 린터 규칙이나 자동 포매터가 그 위험을 없애 줍니다.

규격을 지키는 minifier는 동작을 보존합니다. 단, 입력이 유효한 표준 JavaScript일 때에만 그렇습니다. Terser는 ECMAScript를 파싱합니다. TypeScript나 JSX는 이해하지 못합니다. 이들은 먼저 일반 JS로 트랜스파일해야 하며, 그러지 않으면 파싱 단계에서 최소화가 실패합니다. .ts 파일을 JS minifier에 붙여 넣었는데 오류가 난다면 바로 그 이유입니다.

이름과 관련해 자주 나오는 질문이 있습니다. minify와 uglify의 차이입니다. 사실상 같은 뜻입니다. “Uglify”는 초기에 인기 있던 JS minifier인 UglifyJS에서 왔고, Terser는 ES2015 이상을 지원하는 그 현대적 포크입니다. 오늘날 “minify”는 세 언어 모두에 걸친 일반 용어이고, “uglify”는 같은 과정을 가리키는 더 오래된 JS 전용 이름입니다.

JavaScript minifier는 브라우저에서 Terser를 실행해 지역 변수 이름을 바꾸고, 데드 코드를 떨어뜨리고, 주석을 걷어내며, 각 패스에서 몇 바이트를 절약했는지 보고합니다.

HTML 최소화는 어떻게 작동하나

HTML 최소화는 기본부터 시작합니다. 주석을 제거하고(<!DOCTYPE> 선언과 아직 의존하는 조건부 주석은 유지), 태그 사이의 공백을 없애고, 속성 목록 안의 군더더기 공백을 다듬습니다. 작은 예로 그 모양을 보여 드립니다.

<!-- nav -->
<ul>
  <li><a href="/">Home</a></li>
  <li><a href="/about">About</a></li>
</ul>

이것은 다음이 됩니다.

<ul><li><a href=/>Home</a><li><a href=/about>About</a></ul>

주석은 사라지고, 태그 사이의 들여쓰기는 없어졌으며, 선택적인 </li> 닫는 태그는 떨어졌고, 따옴표 없이 써도 되는 속성 값은 따옴표를 잃었습니다. 여기서부터 minifier는 몇 가지 HTML 고유의 기법을 더 적용할 수 있습니다.

  • 선택적 닫는 태그 제거. HTML 명세는 </li>, </p>, </td> 등 여러 닫는 태그의 생략을 허용하므로, minifier가 이를 떨어뜨릴 수 있습니다.
  • 속성 따옴표 제거. 값에 공백이나 특수 문자가 없으면 class="x"class=x가 됩니다.
  • 불리언 속성 축약. disabled="disabled"는 그냥 disabled가 되고, checked="checked"checked가 됩니다.
  • 내장 CSS와 JS 최소화. <style><script> 블록의 내용도 함께 최소화되므로, 한 번의 패스로 문서 전체가 작아집니다.

여기에 가장 중요한 경계가 있습니다. HTML에서는 공백이 의미를 가질 때가 있습니다. <pre><textarea> 안에서는 모든 공백과 줄바꿈이 글자 그대로 렌더링됩니다. white-space: pre가 적용된 요소도 똑같이 동작합니다. 그리고 인라인 요소 사이의 공백은 레이아웃에 영향을 줍니다. 두 <a> 태그 사이의 공백은 페이지에서 틈으로 나타납니다. 이 공백을 평평하게 만드는 공격적인 최소화는 페이지의 모양을 바꿀 수 있습니다. 그러니 최소화한 뒤에는 배포 전에 pre, textarea, 그리고 인라인 요소 경계 주위의 렌더링을 확인하세요.

HTML minifier는 js-beautify로 포매팅하고, 내장된 스타일과 스크립트는 CSSO와 Terser로 최소화하며, 전부 클라이언트 쪽에서 처리합니다. 압축을 대신해 줄 빌드 단계가 거의 없는 이메일 HTML이나 CMS에서 내보낸 마크업에 특히 유용합니다.

Minify vs Gzip vs Brotli, 어떻게 함께 작동하나

이제 핵심 질문입니다. 서버가 이미 gzip이나 brotli로 서빙한다면 그래도 minify를 해야 할까요? 해야 합니다. 두 기법이 서로 다른 군더더기를 제거하기 때문입니다.

최소화는 소스 수준의 문법적 군더더기를 제거합니다. 사람이 읽기 좋게 하려고 존재하는 공백, 주석, 긴 이름, 장황한 구문입니다. gzip과 brotli는 바이트 수준의 통계적 군더더기를 제거합니다. 파일 전체에서 반복되는 문자열과 패턴이 더 짧은 코드로 치환됩니다. 한쪽은 여러분 코드의 문법을 이해하고, 다른 쪽은 그저 바이트의 흐름을 봅니다. 서로 다른 것을 겨냥하기 때문에 둘을 함께 쓰면 효과가 쌓입니다.

구체적으로 보면, gzip은 번들에서 function 이라는 문자열이 200번 나타나는 것을 알아채고 그때마다 짧은 역참조로 치환하는 데 뛰어납니다. 하지만 getUserPreferencesgetUserSettings가 줄일 수 있는 변수 이름이라는 것은 알지 못하고, if (false) { ... } 블록 전체가 결코 실행되지 않으리라는 것도 알지 못합니다. 최소화가 바로 그것들을 처리합니다. 바이트 수준 압축기가 보지 못하는 구조적·의미적 이득입니다. 둘을 함께 돌리면 각자가 상대방이 보지 못하는 것을 정리합니다.

실제로 일어나는 순서대로 계산을 보면 이렇습니다.

  1. minify 단독은 공백과 주석을 제거하고 문법을 단축해 보통 CSS, JS, HTML을 20–30% 줄입니다.
  2. 최소화된 출력에 gzip을 적용하면 텍스트에 남은 반복 패턴을 인코딩해 60–80%를 더 제거합니다.
  3. gzip 대신 brotli를 쓰면 더 큰 내장 사전과 더 나은 알고리즘 덕분에 출력을 다시 15–25% 더 작게 만듭니다.

요약하면, 먼저 minify하고 그다음 compress하세요. 합쳐진 결과는 원본 소스보다 흔히 80–90% 더 작습니다. 둘은 양자택일이 아니며, 어느 한쪽을 건너뛰면 바이트를 그냥 흘려보내는 셈입니다.

minify가 brotli 위에서도 제 몫을 하는 이유는 세 가지입니다.

  1. 더 작은 입력은 더 작게 압축됩니다. 최소화된 파일은 압축기가 처리할 군더더기 자료를 더 적게 주고, 더 작고 깔끔한 입력은 대체로 더 작은 출력을 냅니다.
  2. minify는 압축이 할 수 없는 일을 합니다. 데드 코드 제거와 짧은 변수 이름은 의미적 제거입니다. gzip은 여러분의 코드를 이해하지 못하고 바이트만 보므로, 사용되지 않는 함수를 지우거나 변수 이름을 바꿀 수 없습니다.
  3. 브라우저가 파싱할 바이트가 줄어듭니다. 압축 해제 후 브라우저가 받는 것은 최소화된 코드입니다. 코드가 적을수록 다운로드가 작아질 뿐 아니라 파싱과 실행도 빨라집니다.

순서는 선택의 문제가 아니라 각 단계가 어디에 속하는지에서 따라 나옵니다. 최소화는 빌드 시점에 속합니다. 여러분이나 빌드 도구가 한 번 합니다. 압축은 전송 시점에 속합니다. 서버가 요청마다 하고, 브라우저가 도착 시 압축을 해제합니다. 그래서 파이프라인은 자연스럽게 minify → 배포 → 서버 압축이 됩니다. 반대 순서는 불가능합니다. 압축된 출력은 더 이상 소스 코드가 아니므로 “압축한 다음 minify”라는 것은 성립하지 않습니다.

“먼저 minify, 그다음 compress”에는 작지만 중요한 단서가 있습니다. 이미 압축된 콘텐츠를 다시 압축하는 것은 무의미하거나 오히려 역효과입니다. 이미 이진이고 엔트로피가 높은 자산, 즉 JPEG, PNG, WebP, WOFF2 폰트는 gzip이나 brotli로 얻을 것이 없으며 애초에 텍스트 압축 규칙에 포함되어서는 안 됩니다. 최소화는 텍스트 전용 변환이므로 그런 파일은 건드리지 않습니다. 선별이 필요한 곳은 압축입니다. 서버가 텍스트 MIME 타입(HTML, CSS, JS, JSON, SVG)을 압축하도록 설정하고, 이미 압축된 이진 파일은 그대로 두세요.

전송 계층을 설정하는 일, 즉 brotli를 켜고 Content-Encoding을 지정하는 일은 서버나 CDN이 담당하는 운영 영역입니다. 이 가이드는 최소화가 일어나는 소스 계층에 머뭅니다. 페이로드를 더 폭넓게 최적화하고 있다면, 같은 “인코딩 계층에서 바이트를 아낀다”는 생각이 이미지에도 적용됩니다. 이미지 포맷 가이드가 WebP/AVIF/JPEG 쪽 이야기를 다룹니다.

수동으로 minify할 필요가 없을 때

많은 minifier 마케팅이 건너뛰는 사실이 하나 있습니다. 빌드 단계가 있다면 프로덕션 출력은 이미 최소화되어 있습니다. 최신 빌드 파이프라인은 이를 자동으로 합니다.

Vite와 esbuild는 JavaScript와 CSS를 기본으로 최소화합니다. Rollup과 webpack은 TerserPluginCssMinimizerPlugin을 통해 합니다. Lightning CSS는 네이티브 속도로 CSS를 처리합니다. Next.js, Astro 같은 프레임워크는 프로덕션 빌드에서 여러분이 따로 손대지 않아도 최소화하고, tree-shake하고, 청크를 분할합니다. 명령은 대개 vite buildnpm run build 정도입니다. 최소화는 “프로덕션용으로 빌드한다”는 말에 이미 포함된 일이지 따로 덧붙이는 단계가 아닙니다. 여러분의 프로젝트가 이 경우라면, 파일을 그 뒤에 별도의 minifier에 한 번 더 통과시키는 것은 잘해야 불필요하고 잘못하면 해롭습니다. 이미 최소화된 코드를 이중으로 mangle하면 혼란스러운 출력이 나올 수 있고 의미 있는 추가 바이트도 절약되지 않습니다.

빌드 도구는 단독 minifier가 할 수 없는 일도 합니다. 전체 의존성 그래프의 맥락 안에서 최소화하는 것입니다. 특히 tree-shaking은 번들러가 모든 import와 export를 볼 수 있고 특정 함수가 결코 사용되지 않음을 증명할 수 있을 때에만 작동합니다. 단일 파일 minifier에는 추론할 그래프가 없습니다. 주어진 파일 안에서는 데드 코드를 떨어뜨릴 수 있지만, 가져온 모듈 전체가 도달 불가능하다는 것은 알 수 없습니다. 프로덕션 최소화의 올바른 자리가 빌드 파이프라인인 또 하나의 이유입니다.

그렇다면 단독 minifier가 맞는 도구인 때는 언제일까요? 대신 처리해 줄 빌드 단계가 없는, 한정된 경우들입니다.

  • 번들러가 관여하지 않는 정적 사이트와 손으로 쓴 단일 파일 페이지.
  • 많은 시스템이 바이트 단위로 요금을 매기고 빌드 파이프라인이 아예 없는 이메일 HTML 템플릿.
  • 다른 사람의 페이지에 끼워 넣는 서드파티 스니펫과 위젯 코드.
  • 빠른 크기 확인. 블록을 붙여 넣고 최소화 후 얼마나 작아지는지, 얼마나 절약했는지 확인할 때입니다. 절약된 바이트 표시가 바로 그 용도입니다.
  • 다른 사람의 최소화된 코드 읽기. 포매터를 거꾸로 돌려 다시 읽을 수 있게 만들 때입니다.

판단은 간단합니다. 빌드가 있나요? 빌드가 minify하게 두세요. 빌드가 없고, 일회성이거나, 그저 크기를 확인하는 거라면 온라인 도구가 가장 빠른 길입니다. 게다가 이 도구들은 전부 브라우저 안에서 실행되므로 여러분의 코드는 기기를 떠나지 않습니다. 독점적이거나 미공개인 코드에는 이 점이 중요합니다. 그런 코드는 모든 것의 사본을 받는 서버 측 포매터에 붙여 넣어서는 안 됩니다. 이 클러스터의 또 다른 포매팅 심층 가이드인 SQL 스타일 가이드와 같은 프라이버시 논거입니다.

Source Map으로 최소화된 코드 디버깅하기

최소화된 코드는 그 자체로는 디버깅하기가 매우 어렵습니다. Terser가 모든 지역 변수를 a, b, c로 이름을 바꾸고 나면, bundle.min.js:1:48211을 가리키는 프로덕션 스택 트레이스는 실제로 무엇이 깨졌는지를 거의 알려 주지 않습니다.

source map이 이를 해결합니다. source map은 최소화된 출력의 각 위치와 원본 소스의 대응 위치 사이의 매핑을 기록한 .map 파일입니다. 브라우저 DevTools가 이를 로드하면, 최소화된 오류를 실제 파일 이름, 줄 번호, 변수 이름으로 되돌려 보여 줍니다. 브라우저가 빌드 결과 코드를 실행하고 있더라도 여러분은 직접 작성한 코드를 기준으로 디버깅하게 됩니다.

실제로는 빌드 도구가 최소화된 번들과 함께 source map을 생성하고, //# sourceMappingURL=bundle.min.js.map 주석(또는 HTTP 헤더)이 브라우저를 그 .map으로 안내합니다. DevTools를 열고 오류를 만나면, 스택 트레이스가 최소화된 뒤죽박죽 대신 실제 파일 이름과 줄 번호를 보여 줍니다. 이 맵은 DevTools가 열려 있을 때에만 지연 로드되므로 방문자에게는 비용이 들지 않습니다.

알아 둘 만한 프라이버시 측면이 있습니다. 공개된 source map은 사실상 DevTools를 여는 누구에게나 원본 소스 코드를 배포하는 셈입니다. 공개 코드라면 괜찮지만 독점 코드라면 그렇지 않습니다. 그래서 숨김(hidden) source map이 있습니다. 번들은 sourceMappingURL 주석을 담지 않으므로 대중은 맵을 보지 못하지만, 여러분은 그것을 Sentry 같은 오류 모니터링 서비스에 업로드할 수 있습니다. 그 서비스가 자기 쪽에서 프로덕션 스택 트레이스를 디민(de-minify)해 주어, 소스를 외부에 노출하지 않고도 읽을 수 있는 오류를 얻습니다.

이것이 앞의 요점을 어떻게 뒷받침하는지 보세요. source map은 빌드 도구의 기능입니다. 평범한 온라인 minifier는 대개 source map을 생성하지 않습니다. 일회성 압축에는 필요 없기 때문입니다. 프로덕션 최소화를 빌드에 맡겨야 하는 또 하나의 이유입니다. 맵을 공짜로 얻을 수 있으니까요. 그리고 source map은 최소화된 번들 자체를 바꾸지 않는다는 점을 기억하세요. 옆에 놓인 디버깅 보조물일 뿐입니다. .map을 프로덕션 의존성으로 착각하지 마세요.

자주 묻는 질문

최소화는 압축과 같은 것인가요?

아닙니다. 최소화(minify)는 여러분의 소스 코드를 다시 씁니다. 공백과 주석을 걷어내고 이름을 줄여서 더 작지만 여전히 유효한 코드로 만듭니다. 압축(gzip, brotli)은 그 결과 바이트를 전송용으로 인코딩하고, 브라우저가 그것을 디코딩합니다. 둘은 서로 다른 군더더기를 처리하고 서로 다른 단계에서 작동하며, 효과가 쌓입니다. 먼저 minify하고 그다음 compress하세요.

gzip이나 brotli를 쓰면 minify가 필요한가요?

네, 필요합니다. gzip과 brotli를 써도 최소화는 여전히 중요합니다. 최소화된 코드는 압축기에 군더더기가 적은 입력을 주므로 더 작게 압축되고, minify는 바이트 수준 압축이 할 수 없는 의미적 제거(데드 코드, 짧은 변수 이름)를 합니다. 브라우저가 파싱할 바이트도 줄어듭니다. 둘 다, 그 순서로 쓰세요.

최소화가 제 코드를 깨뜨리나요?

규격을 지키는 minifier는 동작을 보존합니다. CSS는 동일하게 렌더링되고, Terser는 JavaScript를 등가로 유지합니다. 출력은 소스와 똑같이 실행됩니다. 주의점은 두 가지입니다. 자동 세미콜론 삽입에 의존하는 JavaScript는 유효한 문법이 필요하고, <pre><textarea>처럼 공백에 민감한 HTML은 최소화 후 확인해야 합니다.

minify와 uglify의 차이는 무엇인가요?

JavaScript에서는 사실상 같은 뜻입니다. “Uglify”는 초기에 인기 있던 JS minifier인 UglifyJS에서 왔고, Terser는 현재 문법을 지원하는 그 현대적 포크입니다. 오늘날 사람들은 CSS, JS, HTML에 걸쳐 “minify”를 일반적으로 쓰고, “uglify”는 같은 과정을 가리키는 더 오래된 JS 전용 이름입니다.

개발 단계에서 minify해야 하나요?

아닙니다. 개발이 아니라 프로덕션 빌드를 minify하세요. 최소화된 코드는 읽기 어렵고 디버깅하기 어려우므로, 개발하는 동안에는 온전하고 포맷된 소스가 낫습니다. 빌드 도구(Vite, esbuild, webpack)는 프로덕션용으로 빌드할 때 자동으로 최소화하며, 배포된 번들을 여전히 디버깅할 수 있도록 흔히 source map까지 함께 만듭니다.

최소화는 파일 크기를 얼마나 줄이나요?

최소화 단독으로는 주로 공백과 주석을 제거하고 이름을 줄여 보통 CSS, JS, HTML을 약 20–30% 줄입니다. 위에 gzip이나 brotli를 더하면 합쳐진 결과는 흔히 원본 소스보다 80–90% 더 작습니다. 정확한 수치는 파일에 공백과 군더더기가 얼마나 있었는지에 따라 달라집니다.

태그: minification css javascript html web-performance build-tools code-optimization