프로덕션 환경 최적화: OXC Minifier로 코드 압축하기
OXC Minifier를 사용하여 JavaScript 코드를 압축하는 방법을 배워보세요. 파일 크기를 줄이고 페이지 로딩 속도를 높이며, Terser보다 5배 이상 빠릅니다.
프로덕션 환경 최적화: OXC Minifier로 코드 압축하기
웹 개발에서 모든 KB가 중요합니다. 더 작은 JavaScript 파일은 더 빠른 로딩 속도, 더 적은 대역폭 소비, 더 나은 사용자 경험을 의미합니다.
오늘은 Terser보다 5배 이상 빠른 코드 압축 도구인 OXC Minifier를 배워보겠습니다.
왜 코드 압축이 필요한가요?
숫자로 보기
프로젝트 빌드 후 JS 파일이 다음과 같다고 가정해 봅시다:
| 상태 | 파일 크기 | 4G 네트워크 로딩 | 3G 네트워크 로딩 |
|---|---|---|---|
| 압축 전 | 500 KB | 1.2초 | 4초 |
| 압축 후 | 180 KB | 0.4초 | 1.5초 |
압축 후 64%의 크기 감소! 이는 모바일 사용자에게 특히 중요합니다.
코드 압축이 하는 일
간단한 예시를 보겠습니다:
// 원본 코드 (약 200바이트)
function calculateTotal(items) {
let total = 0;
for (const item of items) {
if (item.price > 0) {
total = total + item.price * item.quantity;
}
}
return total;
}
export { calculateTotal };
압축 후:
// 압축 후 (약 80바이트)
function calculateTotal(e){let t=0;for(const o of e)o.price>0&&(t+=o.price*o.quantity);return t}export{calculateTotal};
압축의 구체적 작업
| 작업 | 설명 |
|---|---|
| 공백 제거 | 스페이스, 줄바꿈, 들여쓰기 제거 |
| 주석 제거 | 모든 주석 내용 제거 |
| 변수명 축약 | 긴 변수명을 짧은 이름으로 변경 |
| 상수 병합 | 변하지 않는 값 병합 |
| 표현식 단순화 | 논리 표현식 최적화 |
| 데드 코드 제거 | 도달할 수 없는 코드 제거 |
Minifier는 무엇을 할 수 있나요?
OXC Minifier는 다음 기능을 제공합니다:
- 코드 압축: 파일 크기 감소
- 코드 난독화: 코드를 읽기 어렵게 만듦 (리버스 엔지니어링 난이도 증가)
- 데드 코드 제거: 사용하지 않는 코드 제거
- Source Map 생성: 디버깅 능력 보존
빠른 시작
설치
npm install @oxc-minifier
기본 사용
import { minifySync } from "@oxc-minifier";
const code = `
function greeting(name) {
const message = "Hello, " + name + "!";
console.log(message);
return message;
}
export { greeting };
`;
const result = minifySync(code);
console.log(result.code);
출력:
function greeting(e){const o="Hello, "+e+"!";return console.log(o),o}export{greeting};
비동기 압축
import { minify } from "@oxc-minifier";
const result = await minify(code, {
compress: true,
mangle: true,
});
console.log(result.code);
실전: 프로젝트 압축하기
코드 압축 과정을 완전히 연습해 봅시다.
1단계: 테스트 프로젝트 생성
mkdir minify-demo
cd minify-demo
npm init -y
npm install @oxc-minifier
mkdir src dist
src/bundle.js 생성:
// src/bundle.js
/**
* 사용자 관리 모듈
* @module userManager
*/
// 설정
const API_BASE_URL = "https://api.example.com/v1";
const DEFAULT_TIMEOUT = 5000;
const MAX_RETRIES = 3;
/**
* 사용자 클래스
*/
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = new Date();
}
getDisplayName() {
return `${this.name} <${this.email}>`;
}
toJSON() {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
};
}
}
/**
* 사용자 서비스
*/
class UserService {
constructor(baseUrl = API_BASE_URL) {
this.baseUrl = baseUrl;
this.cache = new Map();
}
async fetchUser(userId) {
// 캐시 확인
if (this.cache.has(userId)) {
return this.cache.get(userId);
}
// 요청 보내기
const response = await fetch(`${this.baseUrl}/users/${userId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
const data = await response.json();
const user = new User(data.id, data.name, data.email);
// 결과 캐싱
this.cache.set(userId, user);
return user;
}
async updateUser(userId, updates) {
const response = await fetch(`${this.baseUrl}/users/${userId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updates),
});
if (!response.ok) {
throw new Error(`Failed to update user: ${response.status}`);
}
// 캐시 삭제
this.cache.delete(userId);
return this.fetchUser(userId);
}
}
// 기본 인스턴스 생성
const userService = new UserService();
// 내보내기
export { User, UserService, userService, API_BASE_URL };
2단계: 압축 스크립트 생성
minify.js 생성:
// minify.js
import { minifySync } from "@oxc-minifier";
import fs from "fs";
function minifyFile(inputPath, outputPath) {
console.log(`📦 Minifying: ${inputPath}`);
// 소스 파일 읽기
const code = fs.readFileSync(inputPath, "utf-8");
const originalSize = Buffer.byteLength(code, "utf-8");
// 압축
const result = minifySync(code, {
compress: true, // 압축 활성화
mangle: true, // 변수명 난독화 활성화
sourcemap: true, // Source Map 생성
});
// 압축된 코드 작성
fs.writeFileSync(outputPath, result.code);
// Source Map 작성
if (result.map) {
fs.writeFileSync(`${outputPath}.map`, result.map);
}
const minifiedSize = Buffer.byteLength(result.code, "utf-8");
const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
console.log(`✅ Output: ${outputPath}`);
console.log(`📊 Size: ${originalSize} → ${minifiedSize} bytes (${reduction}% reduction)`);
console.log();
}
// 압축 실행
minifyFile("./src/bundle.js", "./dist/bundle.min.js");
3단계: 압축 실행
node minify.js
출력:
📦 Minifying: ./src/bundle.js
✅ Output: ./dist/bundle.min.js
📊 Size: 2048 → 687 bytes (66.5% reduction)
4단계: 압축 결과 확인
dist/bundle.min.js 내용:
const t="https://api.example.com/v1",e=5e3;class s{constructor(t,e,s){this.id=t,this.name=e,this.email=s,this.createdAt=new Date}getDisplayName(){return`${this.name} <${this.email}>`}toJSON(){return{id:this.id,name:this.name,email:this.email,createdAt:this.createdAt.toISOString()}}}class i{constructor(e=t){this.baseUrl=e,this.cache=new Map}async fetchUser(t){if(this.cache.has(t))return this.cache.get(t);const e=await fetch(`${this.baseUrl}/users/${t}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!e.ok)throw new Error(`Failed to fetch user: ${e.status}`);const i=await e.json(),r=new s(i.id,i.name,i.email);return this.cache.set(t,r),r}async updateUser(t,e){const s=await fetch(`${this.baseUrl}/users/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!s.ok)throw new Error(`Failed to update user: ${s.status}`);return this.cache.delete(t),this.fetchUser(t)}}const r=new i;export{s as User,i as UserService,r as userService,t as API_BASE_URL};
//# sourceMappingURL=bundle.min.js.map
설정 옵션 상세
compress - 압축 옵션
minifySync(code, {
compress: {
// 불리언 옵션
booleans: true, // 불리언 최적화
conditionals: true, // 조건 표현식 최적화
dead_code: true, // 데드 코드 제거
drop_console: false, // console.* 제거
drop_debugger: true, // debugger 문 제거
evaluate: true, // 상수 표현식 계산
loops: true, // 루프 최적화
unused: true, // 사용하지 않는 코드 제거
// 숫자 옵션
sequences: true, // 콤마 연산자로 문장 병합
comparisons: true, // 비교 연산 최적화
inline: 2, // 간단한 함수 인라인
passes: 2, // 압축 반복 횟수
},
});
mangle - 난독화 옵션
minifySync(code, {
mangle: {
toplevel: false, // 최상위 변수 난독화 여부
properties: false, // 속성명 난독화 여부
reserved: ["exports", "require"], // 유지할 이름
// 매핑 테이블 출력 (디버깅용)
// output_map: "mangle-map.json",
},
});
format - 형식 옵션
minifySync(code, {
format: {
comments: false, // 모든 주석 제거
beautify: false, // 출력 미화 여부 (디버깅용)
indent_level: 2, // 들여쓰기 수준 (미화 시에만 유효)
},
});
sourcemap - Source Map
// 외부 Source Map 생성
minifySync(code, {
sourcemap: true,
});
// 인라인 Source Map 생성
minifySync(code, {
sourcemap: "inline",
});
// Source Map 생성 안 함
minifySync(code, {
sourcemap: false,
});
번들 도구와 통합
Vite 통합
// vite.config.js
import { defineConfig } from "vite";
export default defineConfig({
build: {
minify: "oxc",
// OXC 압축 옵션
oxc: {
minify: {
compress: true,
mangle: true,
},
},
},
});
Webpack 통합
// webpack.config.js
const OxcMinifyPlugin = require("@oxc-minifier/webpack");
module.exports = {
mode: "production",
optimization: {
minimizer: [
new OxcMinifyPlugin({
compress: true,
mangle: true,
sourcemap: true,
}),
],
},
};
Rollup 통합
// rollup.config.js
import { oxcMinify } from "@oxc-minifier/rollup";
export default {
input: "src/index.js",
output: {
file: "dist/bundle.js",
format: "esm",
sourcemap: true,
},
plugins: [oxcMinify()],
};
성능 비교
테스트 환경
- 파일: 중형 프로젝트의 빌드 파일
- 원본 크기: 1.2 MB
- 장비: MacBook Pro M1
Terser와 비교
// benchmark.js
import { minifySync as oxcMinify } from "@oxc-minifier";
import { minify as terserMinify } from "terser";
import fs from "fs";
const code = fs.readFileSync("./large-bundle.js", "utf-8");
// OXC Minifier
console.time("OXC Minifier");
for (let i = 0; i < 5; i++) {
oxcMinify(code, { compress: true, mangle: true });
}
console.timeEnd("OXC Minifier");
// Terser
console.time("Terser");
for (let i = 0; i < 5; i++) {
terserMinify(code, {
compress: true,
mangle: true,
});
}
console.timeEnd("Terser");
테스트 결과
| 도구 | 압축 시간 | 출력 크기 | 압축률 |
|---|---|---|---|
| Terser | 3.2초 | 420 KB | 65% |
| OXC Minifier | 0.5초 | 435 KB | 63.75% |
OXC Minifier가 Terser보다 약 6배 빠릅니다!
압축률은 약간 낮지만 속도 이점이 뚜렷하며, 대형 프로젝트에서 이득이 더 큽니다.
압축 전후 비교
예시 1: 조건 표현식 최적화
// 원본 코드
function getStatus(user) {
if (user.isActive === true) {
if (user.hasPremium === true) {
return "premium";
} else {
return "active";
}
} else {
return "inactive";
}
}
// 압축 후
function getStatus(e){return e.isActive?!0===e.hasPremium?"premium":"active":"inactive"}
예시 2: 상수 폴딩
// 원본 코드
const SECONDS_PER_MINUTE = 60;
const MINUTES_PER_HOUR = 60;
const SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
function formatDuration(seconds) {
const hours = Math.floor(seconds / SECONDS_PER_HOUR);
const remainingSeconds = seconds % SECONDS_PER_HOUR;
const minutes = Math.floor(remainingSeconds / SECONDS_PER_MINUTE);
return `${hours}h ${minutes}m`;
}
// 압축 후 (상수가 계산됨)
function formatDuration(e){const s=Math.floor(e/3600);return`${s}h ${Math.floor(e%3600/60)}m`}
예시 3: 데드 코드 제거
// 원본 코드
function debug(message) {
if (process.env.NODE_ENV === "development") {
console.log("[DEBUG]", message);
}
}
function doSomething() {
debug("Starting operation");
return "done";
}
// 프로덕션 환경에서 debug 함수 호출이 제거됨
// 압축 후 (NODE_ENV=production 가정)
function doSomething(){return"done"}
자주 묻는 질문
압축 후 코드에서 에러가 발생하나요?
가능한 원인:
eval또는with사용: 이들은 변수 스코프에 영향을 주어 압축이 실패할 수 있음- 특정 속성명 의존: 코드가 속성명 문자열에 의존하면 속성을 난독화하지 마세요
해결 방법:
minifySync(code, {
mangle: {
reserved: ["importantName"], // 특정 이름 유지
},
});
Source Map이 맞지 않아요?
압축 옵션이 번들 도구 설정과 일치하는지 확인하세요:
// Source Map 활성화 확인
minifySync(code, {
sourcemap: true,
});
특정 주석을 유지하고 싶어요?
minifySync(code, {
format: {
comments: /license|copyright/i, // 이 키워드가 포함된 주석 유지
},
});
console을 제외하고 로그를 유지하고 싶어요?
minifySync(code, {
compress: {
drop_console: true, // 모든 console.* 제거
},
});
모범 사례
1. 프로덕션 환경에서 압축 활성화
{
"scripts": {
"build": "vite build --mode production",
"build:dev": "vite build --mode development"
}
}
2. 디버깅용 Source Map 유지
// 프로덕션에서도 Source Map 생성 (에러 모니터링 플랫폼에 업로드)
minifySync(code, {
sourcemap: true,
});
3. 압축과 난독화 분리
// 압축만, 난독화 안 함 (디버깅 단계)
minifySync(code, {
compress: true,
mangle: false,
});
// 완전한 압축 (릴리스 단계)
minifySync(code, {
compress: true,
mangle: true,
});
4. gzip과 함께 추가 압축
# 압축 후 gzip
oxc-minify bundle.js -o bundle.min.js
gzip -k bundle.min.js
# 최종 파일: bundle.min.js.gz
요약
본문에서는 OXC Minifier의 핵심 사용법을 소개했습니다:
| 내용 | 설명 |
|---|---|
| 코드 압축 | 파일 크기 60%+ 감소 |
| 변수명 난독화 | 리버스 엔지니어링 난이도 증가 |
| Source Map | 디버깅 능력 보존 |
| 성능 우위 | Terser보다 5배 이상 빠름 |
Minifier의 핵심 가치: 사용자가 더 적게 다운로드하고, 더 빠르게 로딩.
다음 단계
Minifier의 더 많은 설정 옵션을 알고 싶으신가요? **OXC 한국어 문서 - Minifier 챕터**를 방문하세요.
다음 글에서는 OXC Resolver를 배워보겠습니다. 모듈 경로 해석 뒤의 마법을 이해해 봅시다!
💡 관련 읽기: