Files
2nd/10_Wiki/Topics/Coding/Perf_Bundle_Analysis.md
T
2026-05-09 21:08:02 +09:00

6.0 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
perf-bundle-analysis JS Bundle Analysis — Tree-shake / Split / Size budget Coding draft B conceptual 2026-05-09 2026-05-09
performance
bundle
vite
webpack
vibe-coding
language applicable_to
TS / Vite / Webpack
Frontend
bundle analysis
tree shake
code split
dynamic import
size limit
source map explorer

Bundle Analysis

1MB+ JS bundle = 모바일 3G = 10초+ load. 분석 → tree-shake → split → lazy → size budget. CI 가 회귀 차단.

📖 핵심 개념

  • Tree-shake: 안 쓴 export 제거.
  • Code split: route / component 별 chunk.
  • Dynamic import: 사용 시 로드.
  • Size budget: CI gate.

💻 코드 패턴

Vite — visualizer

yarn add -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

plugins: [
  visualizer({ open: true, gzipSize: true, brotliSize: true }),
];
yarn build
# stats.html 자동 open — sunburst chart

Source map explorer

npx source-map-explorer dist/assets/*.js

→ 각 module 의 byte 시각화.

Webpack — bundle analyzer

yarn add -D webpack-bundle-analyzer
// webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [new BundleAnalyzerPlugin({ analyzerMode: 'static' })];

Next.js

ANALYZE=true yarn build
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true' });
module.exports = withBundleAnalyzer({});

Tree-shake 깨지는 패턴

// ❌ default export + barrel
import lib from 'lib';   // 모든 거 import
import { fn } from 'lib'; // 더 좋음 — but lib 가 sideEffects 인지

// ❌ side effects (top-level)
console.log('module loaded'); // tree-shake 차단

// ❌ require (dynamic)
const x = require(`module-${name}`);

// ✅ Named ESM imports + sideEffects: false
// package.json — library publishing
{
  "sideEffects": false  // 또는 ["./styles.css"]
}

→ Bundler 가 안전하게 제거.

Dynamic import (route split)

// React lazy
const Heavy = lazy(() => import('./Heavy'));

<Suspense fallback={<Spinner />}>
  <Heavy />
</Suspense>
// 일반 dynamic
button.onclick = async () => {
  const { default: heavy } = await import('./heavy');
  heavy.run();
};

Route-level split (Vite)

// react-router 6+
const router = createBrowserRouter([
  {
    path: '/admin',
    lazy: async () => {
      const { AdminPage } = await import('./AdminPage');
      return { Component: AdminPage };
    },
  },
]);

Manual chunks

// vite.config.ts
build: {
  rollupOptions: {
    output: {
      manualChunks: {
        react: ['react', 'react-dom'],
        query: ['@tanstack/react-query'],
        editor: ['@tiptap/core', '@tiptap/react'],
      },
    },
  },
},

Library swap (큰 → 작은)

moment (200KB) → date-fns (modular, ~10KB used)
lodash (full) → lodash-es / specific imports
react-icons (모두) → 단일 svg 또는 lucide-react (tree-shake)
material-ui (?? KB) → headless UI + Tailwind
// ❌
import _ from 'lodash';  // 모두

// ✅
import debounce from 'lodash/debounce';
// 또는 lodash-es
import { debounce } from 'lodash-es';

CDN / external (큰 lib)

// vite.config — 일부 lib 외부화
build: {
  rollupOptions: {
    external: ['react', 'react-dom'],
    output: {
      globals: { react: 'React', 'react-dom': 'ReactDOM' },
    },
  },
},
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>

→ Bundle 작아짐 + CDN cache.

Modern build (Vite 자동)

  • ES module syntax 그대로 (browser native).
  • 옛 browser 만 polyfill (@vitejs/plugin-legacy).

Size budget (size-limit)

yarn add -D size-limit @size-limit/preset-app
// package.json
{
  "size-limit": [
    { "path": "dist/**/*.{js,css}", "limit": "300 KB" },
    { "path": "dist/index-*.js", "limit": "150 KB" }
  ],
  "scripts": {
    "size": "size-limit"
  }
}
yarn size
# CI 에서 강제

Bundlephobia (NPM 패키지 사이즈)

# 새 lib 추가 전
# https://bundlephobia.com/package/<lib>

→ 의존 추가 결정.

Gzip / Brotli compression

gzip on;
gzip_types text/css application/javascript application/json;
gzip_min_length 1024;

brotli on;
brotli_types text/css application/javascript application/json;

→ 30-70% 추가 절감.

Async lib (dynamic only)

// 무거운 lib = 사용자 인터랙션 시만
let echarts: typeof import('echarts') | null = null;

async function showChart() {
  if (!echarts) echarts = await import('echarts');
  echarts.init(...);
}

Image vs JS

JS 100KB → parse + execute = 실제 시간 큼.
Image 100KB → 그냥 바이트.

→ JS 가 비용 더 높음. 줄이기 우선.

🤔 의사결정 기준

작업 도구
분석 Vite visualizer / Source map explorer
Route split React.lazy / loader
큰 lib → 작은 bundlephobia 비교
Library export 검사 sideEffects: false
CI 회귀 size-limit
Modern stack Vite/Rollup (vs Webpack legacy)

안티패턴

  • 모든 거 한 bundle: 첫 load 큼.
  • import * as: tree-shake 어려움.
  • CSS-in-JS 큰 lib: runtime 비용. Tailwind 또는 vanilla extract.
  • 모든 component lazy: waterfall — 첫 paint 느림.
  • Polyfill 모든 browser: 작은 점유율 위해 큰 cost.
  • Bundle analysis 안 함: 큰 lib 모름.
  • Source map prod 노출: 코드 leak. 또는 sentry 만.

🤖 LLM 활용 힌트

  • Vite visualizer 매 빌드 + size-limit CI.
  • Route lazy + manual chunks.
  • 큰 lib = bundlephobia + 작은 alternative.

🔗 관련 문서