⚡ Electron7. 성능 & 메모리03 · Bundle size — 100MB+ 배포의 출처

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-builderelectronLanguages
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"
  ]
}

asartar와 비슷한 단일 파일 컨테이너. 압축은 안 하지만 (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-builderelectronLanguages지원할 언어만 남긴다.

{
  "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가 쓰는 시작 시간 절반 자르기 기법.