03 · Bundle size — 100MB+ 배포의 출처
질문: 우리 앱 코드는 5MB인데 설치 파일은 220MB다. 어디서 나오는가? 한 줄 답: Electron 코어(Chromium + Node + Electron 자체)가 이미 ~100MB다 — 이 부분은 줄일 수 없다. 그 위에 얹는 우리 코드 + node_modules가 보통 20-150MB이고, 여기서 50~80%를 깎는 것이 현실적 목표.
Pyramid Top
설치 파일 220MB의 고정 비용과 변동 비용을 가른다. Chromium 엔진 + Node.js 런타임 + Electron 바인딩 = 고정 100MB는 우리가 할 수 있는 게 없다. 변동 비용 — 우리 src/ + 통째로 들어간 node_modules dev deps + 불필요 asset — 이 곳을 깎는다.
작은 앱이라면 4-5가지 기법으로 50%까지 줄일 수 있다. Electron 자체보다 가벼운 앱이 되는 건 불가능하다 — 그게 Tauri로 가는 결정의 출발점.
사고 흐름
Why — 100MB의 고정 비용
Electron이 패키지에 항상 포함하는 것들:
| 구성 요소 | 크기 (대략) | 줄일 수 있나? |
|---|---|---|
Chromium 엔진 (libchrome_*, v8) | ~70MB | 불가능 — Electron 빌드의 본체 |
| Node.js 런타임 | ~15MB | 불가능 |
Electron 자체 (Electron Framework) | ~10-15MB | 불가능 |
ICU 데이터 (icudtl.dat) | ~10MB | 일부 가능 (small-icu 빌드, 권장 X) |
| Chromium 100+ locale 파일 | ~15MB | 가능 — electron-builder의 electronLanguages |
| ffmpeg | ~3MB | 일부 가능 (codec 제거 빌드) |
Electron 자체 빌드가 ~110MB. 빈 HelloWorld 앱의 설치 파일이 180MB 정도 나오는 이유다 (압축 풀고 + 코드 사이닝 메타데이터 + DMG 포맷 오버헤드).
이 100MB는 우리 코드가 아니라 런타임의 권리비다. “Electron이 무겁다”의 진실이 여기에 있다 — 런타임 통째 배포가 모델의 본질이다.
What — 변동 비용 줄이는 5가지
① npm prune --production — 가장 큰 자리
devDependencies(typescript, eslint, jest, …)는 런타임에 필요 없다. 그러나 electron-builder가 통째로 복사하면 패키지에 들어간다.
# 배포 빌드 전
npm prune --production
# 또는 빌드 도구가 자동 처리:
electron-builder build # 자체적으로 prune 실행two-package.json 패턴: 루트에 dev deps,
app/폴더에 runtime deps만.electron-builder의 표준 구조.
② asar 압축 — 우리 코드의 컨테이너
// electron-builder.json
{
"asar": true, // 기본값 — 항상 켤 것
"asarUnpack": [ // 네이티브 모듈은 unpack
"**/node_modules/sharp/**",
"**/*.node"
]
}asar는 tar와 비슷한 단일 파일 컨테이너. 압축은 안 하지만 (mmap을 위해) — 파일 시스템 entry 수를 줄여 로딩이 빨라지고 분산된 작은 파일을 한 파일로 모은다. macOS의 signing 단위도 단일 파일이라 빠르다.
asar 안의 네이티브 모듈은 로드 실패 —
.node파일은 OS dlopen에 실제 파일 경로가 필요.asarUnpack으로 빼낸다.
③ electron-builder files glob — 안 쓰는 거 빼기
{
"files": [
"dist/**/*", // 빌드된 결과만
"package.json",
"!**/*.{md,map,ts}", // 소스맵·markdown 제외
"!**/test/**",
"!**/.eslintrc*"
]
}이걸 안 하면 모든 README, 모든 test fixture, 모든 .ts source가 패키지에 들어간다. 대형 라이브러리(@babel, lodash)는 docs와 test가 실제 코드의 2-3배다.
④ Renderer 코드는 webpack/Vite 번들 + minify
// vite.config.js (또는 webpack.config.js)
export default {
build: {
minify: 'terser',
rollupOptions: {
output: { manualChunks: { /* code split */ } }
}
}
};- React 1MB → minify + gzip ≈ 200KB
- lodash 70KB → tree shake 후 5KB
- moment.js (220KB) → date-fns (15KB) 교체
Renderer 번들은 cold start에도 영향. 작을수록 ④ JS 평가도 빠르다.
⑤ Chromium locale 제거
electron-builder의 electronLanguages로 지원할 언어만 남긴다.
{
"electronLanguages": ["en", "ko", "ja"]
}기본은 100+ 언어 → ~12MB. 3개 언어만 남기면 ~400KB.
How — 측정과 비교
빌드 결과 들여다보기
# macOS: 빌드 후 .app 안 들여다보기
du -sh dist/mac/MyApp.app/Contents/Frameworks/Electron\ Framework.framework
du -sh dist/mac/MyApp.app/Contents/Resources/app.asar
du -sh dist/mac/MyApp.app/Contents/Resources/app.asar.unpacked
# asar 내용 분석
npx asar list dist/mac/MyApp.app/Contents/Resources/app.asar | head -30실측 비교 (배포 파일 크기)
| 앱 | 배포 크기 | 분석 |
|---|---|---|
| VS Code | ~95MB (macOS arm64) | locale 최소화 + asar 알차게 + 거의 모든 dev 의존성 제거 |
| Slack | ~150MB | 미디어 codec + React + 비디오 처리 |
| Discord | ~180MB | 네이티브 모듈 다수(WebRTC) + 거대한 asset |
| Notion | ~140MB | 거의 외부 SPA라 자체 코드는 작지만 image asset 많음 |
| 빈 Electron | ~180MB | 의외로 작은 앱이 더 큰 경우 — 압축·prune이 안 되어서 |
“우리 앱이 빈 Electron보다 작다”가 가능한 이유: prune·asar·minify를 우리는 했고 비교 대상은 안 했기 때문.
What-if — 무관심의 결과
| 상황 | 결과 |
|---|---|
npm prune --production 안 함 | 220MB → typescript·@types/* 80MB가 그대로 들어감 |
asar 비활성화 | 5만 개 파일이 그대로 → 첫 로딩 수 초 추가 + 코드 사이닝 분 단위 느림 |
| 이미지 원본을 그대로 (PNG 4K) | asset만 100MB. WebP·AVIF 변환으로 70% 절감 |
| Renderer minify 끄고 빌드 | source가 그대로 → 번들 4배 + cold start 30% 느림 |
| Chromium 모든 locale 포함 | +12MB. 글로벌 사용자라도 대부분 5개 언어 미만만 사용 |
node_modules 통째로 복사 | 모듈마다 README·test·examples 폴더 → 실제 코드의 2-3배 |
흥미로운 이야기
VS Code가 95MB가 된 이유 — Microsoft의 바이너리 크기 경쟁은 사실
VS Code 팀은 매 릴리스마다 번들 크기 회귀를 KPI로 추적한다. 깃허브 이슈
microsoft/vscode#XXXX에 “Bundle size grew 3MB this release, investigating”이 정기적으로 올라온다. 그들이 95MB를 유지한 방법: (1)asar에 들어갈 파일 목록을 명시적 allowlist로 —files: ['out/**']만, deny가 아니라 allow. (2) Node 의존성을 수동으로 prune하는 스크립트. (3) Extension Host의 node_modules는 사용자가 설치하는 extension에 들어 있고 본 빌드에는 없음. (4) Chromium 각 OS별 빌드를 분리 — Universal 바이너리(arm64+x64 합본)는 2배가 되므로 안 만든다. 반대 사례: 한 유명 노트 앱이 한때 400MB까지 부풀었다. 원인은 — 모든 의존성을 통째로 + 모든 이미지 원본을 4K + dev source map까지 포함. 사용자 항의 후 6개월에 걸쳐 180MB로 줄였고, 그 과정에서 사용자 만족도가 측정 가능하게 올라갔다. 크기는 사용자 신뢰의 proxy다.
Insight — 못 줄이는 100MB와 줄일 수 있는 120MB
고정 비용은 런타임을 통째로 배포하는 모델의 가격이다. 줄이고 싶으면 모델을 바꿔야 한다 — 그게 Tauri(OS webview, ~10MB)의 결정.
변동 비용은 우리 게으름이다. 5가지 기법으로 50% 이상 줄어든다.
한 단락 요약
Electron 배포 크기 = 고정 ~100MB(Chromium + Node + Electron) + 변동 ~120MB(우리 코드 + node_modules + asset). 고정은 못 줄이고, 변동은 5가지 기법으로 50~80% 절감 — npm prune · asar · files glob · minify · locale 제거. Electron 자체보다 작은 앱은 만들 수 없다 (그래서 작은 앱은 Tauri 후보). 그러나 비슷한 기능의 다른 Electron 앱보다 작은 앱은 5가지 결정만으로 만들 수 있다. 다음: 04-v8-snapshot — Atom·VS Code가 쓰는 시작 시간 절반 자르기 기법.