코딩 가이드/JavaScript/Claude Sonnet 4.6
Claude Sonnet 4.6

Claude Sonnet 4.6

#1 / 5 49.7s 4273
8.4

Anthropic의 최신 소넷 모델

AI 평가

명확성8.8
완성도8.5
관련성8.3
창의성7.8

교차 평가 종합 점수

전체 응답

무엇을 왜 만드는가

느린 자바스크립트 번들은 사용자 경험을 저하시키고 Core Web Vitals 점수를 떨어뜨리는 주범입니다. 이 가이드에서는 Webpack 5의 코드 분할(code splitting) 설정을 통해, 모든 경로에서 2MB에 달하는 거대한 단일 파일(monolith)을 로드하는 대신 현재 페이지에 꼭 필요한 코드만 불러오는 방법을 알아봅니다. 시작하기 전에 Node.js 18 이상 버전과 자바스크립트 모듈에 대한 기본적인 이해, 그리고 npm이 설치되어 있어야 합니다.

프로젝트 설정하기

빈 디렉토리에서 시작하여 Node 프로젝트를 초기화합니다. Webpack과 함께 번들을 분석하고 분할하는 데 필요한 도구들을 설치하겠습니다.

mkdir webpack-splitting-demo
cd webpack-splitting-demo
npm init -y
npm install --save-dev webpack webpack-cli webpack-bundle-analyzer html-webpack-plugin
npm install lodash-es date-fns

여기서는 lodash-esdate-fns를 사용합니다. 이 패키지들은 부주의하게 임포트할 경우 번들 크기를 크게 키우는, 실제 개발 환경에서 흔히 접할 수 있는 무거운 의존성 파일들입니다. 번들 분석기를 통해 그 결과를 시각적이고 구체적으로 확인해 볼 것입니다.

다음 프로젝트 구조를 수동으로 만들거나 아래 쉘 명령어를 사용하여 생성하세요.

mkdir -p src/pages src/utils
touch src/index.js src/pages/dashboard.js src/pages/reports.js src/utils/formatter.js webpack.config.js

최종 구조는 다음과 같아야 합니다.

webpack-splitting-demo/
├── src/
│   ├── index.js
│   ├── pages/
│   │   ├── dashboard.js
│   │   └── reports.js
│   └── utils/
│       └── formatter.js
├── webpack.config.js
└── package.json

애플리케이션 코드 작성하기

유틸리티 모듈은 날짜 포맷팅을 담당합니다. 필요한 특정 date-fns 함수만 임포트하는데, 이는 나중에 트리 쉐이킹(tree-shaking)이 효과적으로 작동하게 만드는 패턴입니다.

// src/utils/formatter.js
import { format, parseISO } from "date-fns";

export function formatDate(isoString) {
  const parsed = parseISO(isoString);
  return format(parsed, "MMMM dd, yyyy");
}

export function formatCurrency(amount, currency = "USD") {
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
}

대시보드 페이지는 차트 로직 등을 포함하는 비교적 무거운 모듈입니다. 여기서는 실제 환경의 무게감을 재현하기 위해 lodash-es를 임포트하여 시뮬레이션합니다.

// src/pages/dashboard.js
import { groupBy, sumBy } from "lodash-es";
import { formatCurrency, formatDate } from "../utils/formatter.js";

const transactions = [
  { category: "food", amount: 42.5, date: "2024-11-01" },
  { category: "transport", amount: 15.0, date: "2024-11-02" },
  { category: "food", amount: 28.75, date: "2024-11-03" },
  { category: "utilities", amount: 120.0, date: "2024-11-04" },
];

export function renderDashboard(container) {
  const grouped = groupBy(transactions, "category");

  const summaryHTML = Object.entries(grouped)
    .map(([category, items]) => {
      const total = sumBy(items, "amount");
      return `
${category} ${formatCurrency(total)} Last: ${formatDate(items.at(-1).date)}
`; }) .join(""); container.innerHTML = `

Spending Summary

${summaryHTML}
`; }
// src/pages/reports.js
import { formatDate, formatCurrency } from "../utils/formatter.js";

const reportData = [
  { month: "2024-09-01", revenue: 18400, expenses: 12200 },
  { month: "2024-10-01", revenue: 21300, expenses: 14800 },
  { month: "2024-11-01", revenue: 19750, expenses: 11900 },
];

export function renderReports(container) {
  const rows = reportData
    .map(
      ({ month, revenue, expenses }) =>
        `
          ${formatDate(month)}
          ${formatCurrency(revenue)}
          ${formatCurrency(expenses)}
          ${formatCurrency(revenue - expenses)}
        `
    )
    .join("");

  container.innerHTML = `
    

Monthly Reports

${rows}
MonthRevenueExpensesProfit
`; }

엔트리 포인트(Entry point)는 실제 코드 분할이 일어나는 곳입니다. 동적 import()는 Webpack에게 각 페이지를 별도의 청크(chunk)로 생성하도록 지시하며, 사용자가 해당 페이지로 이동할 때만 로드됩니다.

// src/index.js
const appContainer = document.createElement("div");
appContainer.id = "app";
document.body.appendChild(appContainer);

const nav = document.createElement("nav");
nav.innerHTML = `
  
  
`;
document.body.prepend(nav);

async function loadPage(route) {
  appContainer.innerHTML = "

Loading...

"; try { if (route === "dashboard") { const { renderDashboard } = await import( /* webpackChunkName: "page-dashboard" */ "./pages/dashboard.js" ); renderDashboard(appContainer); } else if (route === "reports") { const { renderReports } = await import( /* webpackChunkName: "page-reports" */ "./pages/reports.js" ); renderReports(appContainer); } } catch (error) { appContainer.innerHTML = `

Failed to load page: ${error.message}

`; } } nav.addEventListener("click", (event) => { const route = event.target.dataset.route; if (route) loadPage(route); }); loadPage("dashboard");

⚠️ 참고: /* webpackChunkName: "..." */ 매직 주석(magic comment)은 단순한 장식이 아닙니다. 이는 해당 청크의 출력 파일 이름을 결정합니다. 이 주석이 없으면 Webpack은 0.bundle.js와 같은 숫자 ID를 생성하여 디버깅을 어렵게 만듭니다.

코드 분할을 위한 Webpack 설정

Webpack의 optimization.splitChunks 설정은 자동 청크 생성의 핵심 엔진입니다. 또한 각 출력 파일 내부에 무엇이 들어있는지 정확히 확인할 수 있도록 번들 분석기를 플러그인으로 구성하겠습니다.

// webpack.config.js
import path from "path";
import { fileURLToPath } from "url";
import HtmlWebpackPlugin from "html-webpack-plugin";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const isAnalyze = process.env.ANALYZE === "true";

export default {
  mode: "production",
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js",
    chunkFilename: "[name].[contenthash].chunk.js",
    clean: true,
  },
  optimization: {
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendorLodash: {
          test: /[\\/]node_modules[\\/]lodash-es[\\/]/,
          name: "vendor-lodash",
          chunks: "all",
          priority: 20,
        },
        vendorDateFns: {
          test: /[\\/]node_modules[\\/]date-fns[\\/]/,
          name: "vendor-date-fns",
          chunks: "all",
          priority: 20,
        },
        sharedUtils: {
          test: /[\\/]src[\\/]utils[\\/]/,
          name: "shared-utils",
          chunks: "all",
          minChunks: 2,
          priority: 10,
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({ title: "Webpack Splitting Demo" }),
    ...(isAnalyze ? [new BundleAnalyzerPlugin()] : []),
  ],
};

cacheGroups 항목들은 각각의 역할을 수행합니다. vendorLodash는 lodash를 별도의 청크로 격리하여 앱 코드와 별개로 캐싱되도록 하고, sharedUtils는 두 페이지 모두에서 사용하는 포맷터 모듈을 추출하여 중복 번들링을 방지합니다.

package.json에 빌드 스크립트를 추가합니다.

{
  "type": "module",
  "scripts": {
    "build": "webpack",
    "build:analyze": "ANALYZE=true webpack"
  }
}

⚠️ 참고: webpack.config.js에서 ES 모듈의 import/export 문법을 사용하므로 "type": "module" 필드가 필요합니다. 만약 CommonJS 방식을 선호한다면 파일 이름을 webpack.config.cjs로 바꾸고 require()/module.exports를 사용하세요.

테스트 및 검증

프로덕션 빌드를 실행하고 Webpack이 무엇을 생성하는지 확인해 봅시다.

npm run build

터미널에 다음과 유사한 출력이 나타날 것입니다.

asset main.4a3f91bc.js                  3.2 KiB
asset page-dashboard.7d2e1a4f.chunk.js  1.4 KiB
asset page-reports.c8b03e12.chunk.js    0.9 KiB
asset vendor-lodash.f2c14a88.chunk.js   68.1 KiB
asset vendor-date-fns.3a91bc7d.chunk.js 18.4 KiB
asset shared-utils.b4e20c11.chunk.js    0.8 KiB

초기 페이지 로드 시에는 약 3KB 정도의 main.js만 가져옵니다. lodash와 date-fns 청크는 사용자가 탐색 버튼을 처음 클릭할 때만 다운로드되며, 이후 방문 시에는 브라우저에 의해 캐싱됩니다.

크롬 개발자 도구를 열고 Network 탭에서 JS 필터를 걸어 분할이 제대로 작동하는지 확인하세요. Dashboard 버튼을 누르면 page-dashboardvendor-lodash가 필요에 따라 로드되는 것을 볼 수 있습니다. Reports를 클릭하면 page-reports만 가져옵니다. date-fns 벤더 청크는 두 페이지가 공유하므로 대시보드 로드 시 이미 캐싱되었기 때문입니다.

전체 시각적 분석을 위해 번들 분석기를 실행해 보세요.

npm run build:analyze

브라우저에 각 청크 안에 어떤 모듈이 있는지 보여주는 트리맵이 열립니다. 만약 date-fns가 별도의 벤더 청크가 아닌 page-dashboardpage-reports 양쪽 내부에 나타난다면, cacheGroups 설정에 우선순위 충돌이 있는 것입니다. 이 경우 vendorDateFns의 priority 값을 20보다 크게 높여서 해결하세요.

⚠️ 참고: 파일 이름의 contenthash는 캐시 버스팅(cache busting)에 필수적입니다. 대시보드 페이지 코드만 수정할 경우 page-dashboard.[hash].chunk.js의 해시값만 변경됩니다. 따라서 사용자는 전체 벤더 번들이 아닌 수정된 해당 파일만 다시 다운로드하게 됩니다.

결과 요약 및 향후 단계

이제 여러분은 외부 라이브러리를 캐시 가능한 별도 청크로 분할하고, 공통 유틸리티를 추출하여 중복을 방지하며, 페이지 수준의 코드는 필요할 때만 로드하는 Webpack 5 설정을 갖추게 되었습니다. 이 모든 것은 동적 import() 호출과 명시적인 cacheGroups 구성을 통해 이루어집니다. 여기서 더 나아가 브라우저 유휴 시간에 청크를 미리 로드하는 webpackPrefetch: true 주석을 추가하거나, 개발을 위한 webpack-dev-server 설정, 그리고 스타일시트에도 동일한 분할 전략을 적용하기 위한 css-minimizer-webpack-plugin 통합 등을 시도해 볼 수 있습니다.

comparison.translatedBy

다른 AI의 평가

Gemini 3 Flash
9.0
명확성
9.0
완성도
9.0
관련성
9.0
창의성
9.0
Overall
9.0

이게 단연 최고입니다. lodash와 date-fns 같은 실무적인

번역 보기
DeepSeek V3.2
8.8
명확성
9.0
완성도
9.0
관련성
9.0
창의성
8.0
Overall
8.8

단연 돋보이는 아티클입니다. 실무 개발자가 쓴

번역 보기
Grok 4
8.8
명확성
9.0
완성도
9.0
관련성
8.0
창의성
9.0
Overall
8.8

C 아티클은 라우트 동적 임포트를 활용한 실용적인

번역 보기
GPT-5.2
7.0
명확성
8.0
완성도
7.0
관련성
7.0
창의성
5.0
Overall
7.0

예제 앱이 라우팅, 동적 임포트, date-fns/

번역 보기