GPT-5.2

GPT-5.2

#3 / 5 51.4s 4491
7.1

OpenAI의 최신 플래그십 모델

AI 평가

명확성7.3
완성도8.3
관련성6.0
창의성7.3

교차 평가 종합 점수

전체 응답

사용자가 필요할 때만 "무거운" 기능을 로드하는 소규모 Webpack 기반 JavaScript 앱을 만들어 보겠습니다. 코드 분할(code splitting)과 동적 import()를 사용하여 초기 번들 크기를 줄이고 중요하지 않은 코드의 로드를 지연시킴으로써 초기 구동 시간을 직접적으로 개선할 수 있습니다. 사전 요구 사항: Node.js 18+ (또는 20+), 기본적인 ES 모듈 지식, 터미널 명령어 실행에 대한 익숙함이 필요합니다.

설정: 프로젝트 구조 생성 + Webpack 설치

이 설정은 하나의 엔트리 파일, 하나의 HTML 파일, 그리고 해시된 번들을 출력하는 Webpack 설정을 포함하는 최소한의 재현 가능한 베이스라인을 생성합니다. 코드 분할은 여러 파일을 생성하므로, 캐시 버그 없이 장기 캐싱(long-term caching)을 적용하려면 해시가 중요합니다.

mkdir webpack-code-splitting-demo
cd webpack-code-splitting-demo
npm init -y

npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin clean-webpack-plugin

mkdir -p src
touch src/index.js src/heavy-math.js src/analytics.js src/styles.css

다음으로, 개발 서버를 실행하고 프로덕션 빌드를 생성할 수 있도록 스크립트를 추가합니다. 개발 서버는 동적 청크(dynamic chunks)가 필요할 때만 요청되는지 확인하는 가장 빠른 방법입니다.

{
  "name": "webpack-code-splitting-demo",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production",
    "analyze:stats": "webpack --mode production --profile --json > dist/stats.json"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "html-webpack-plugin": "^5.6.0",
    "webpack": "^5.90.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.0.4"
  }
}

Webpack이 스크립트를 주입할 HTML 템플릿을 생성합니다. HtmlWebpackPlugin은 해시된 청크 이름을 포함하여 올바른 번들 파일 이름을 자동으로 추가해 주므로 스크립트 태그를 직접 코딩할 필요가 없습니다.

<!-- src/index.html -->
<!doctype html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Webpack Code Splitting Demo</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

앱이 실행 중인지 시각적으로 확인할 수 있도록 아주 작은 스타일시트를 추가합니다. Webpack은 CSS도 처리할 수 있지만, 이 가이드에서는 코드 분할에 집중하기 위해 나중에 간단한 방식으로 주입하겠습니다.

/* src/styles.css */
:root {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}

#app {
  max-width: 820px;
  margin: 40px auto;
  padding: 16px;
}

button {
  margin-right: 12px;
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid #ddd;
  background: #fff;
  cursor: pointer;
}

pre {
  background: #f6f8fa;
  padding: 12px;
  border-radius: 8px;
  overflow: auto;
}

⚠️ 참고: package.json"type": "module" 설정으로 인해 Node.js는 webpack.config.js도 ESM으로 처리합니다. CommonJS 설정을 선호한다면 "type": "module"을 제거하고 설정 파일에서 require()를 사용하세요.

구현 1: 청크 분할 및 장기 캐싱을 적용한 Webpack 설정

이 설정은 성능에 초점을 맞춘 세 가지 작업을 수행합니다. 이전 빌드 결과물을 삭제하고, 올바른 스크립트 태그가 포함된 HTML을 생성하며, 공통 코드를 캐싱할 수 있도록 청크 분할을 활성화합니다. 또한 콘텐츠 해시(content-hashed)가 포함된 파일명을 출력하여 배포 후에도 브라우저가 안정적인 파일을 캐싱할 수 있게 합니다.

import path from "node:path";
import { fileURLToPath } from "node:url";
import HtmlWebpackPlugin from "html-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: path.join(__dirname, "src", "index.js"),
  output: {
    path: path.join(__dirname, "dist"),
    filename: "assets/[name].[contenthash:8].js",
    chunkFilename: "assets/[name].[contenthash:8].js",
    publicPath: "/",
  },
  devtool: "source-map",
  devServer: {
    static: {
      directory: path.join(__dirname, "dist"),
    },
    port: 3000,
    open: true,
    hot: true,
    historyApiFallback: true,
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "src", "index.html"),
    }),
  ],
  optimization: {
    runtimeChunk: "single",
    splitChunks: {
      chunks: "all",
      minSize: 10_000,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        type: "asset/source",
      },
    ],
  },
  resolve: {
    extensions: [".js"],
  },
};

UI를 렌더링하기 전이라도 베이스라인이 작동하는지 확인하기 위해 개발 서버를 실행해 보세요. 아직 #app을 채우지 않았으므로 빈 페이지가 보이는 것이 정상입니다.

npm run dev

예상 결과: 브라우저에서 http://localhost:3000이 열리고 콘솔에 오류 없이 빈 페이지가 표시됩니다. 이 단계에서 네트워크(Network) 탭에는 하나의 메인 번들 요청(그리고 런타임 청크)이 표시되어야 합니다.

⚠️ 참고: 번들에 대해 404 오류가 발생한다면 대개 publicPath 설정이 맞지 않는 경우입니다. 개발 서버에서는 publicPath: "/"를 유지하고, 하위 디렉토리에서 앱을 서빙하는 경우가 아니라면 이를 조정하지 마세요.

구현 2: "무거운" 기능을 위한 동적 import (진정한 코드 분할)

이제 두 개의 버튼이 있는 UI를 만듭니다. 하나는 무거운 계산 모듈을 실행하고, 다른 하나는 분석(analytics) 모듈을 로드합니다. 핵심은 클릭 핸들러 내부에서 import()를 사용하여 Webpack이 별도의 청크를 생성하고 필요할 때만 다운로드하도록 하는 것입니다.

import stylesText from "./styles.css";

function injectStyles(cssText) {
  const style = document.createElement("style");
  style.textContent = cssText;
  document.head.append(style);
}

function renderApp() {
  const app = document.querySelector("#app");
  app.innerHTML = `
    <h2>Webpack Code Splitting Demo</h2>
    <p>개발자 도구 → 네트워크 탭을 여세요. 버튼을 클릭하면 새로운 청크 파일이 필요할 때만 다운로드되는 것을 볼 수 있습니다.</p>

    <div>
      <button id="btn-calc">무거운 계산 실행</button>
      <button id="btn-analytics">분석 도구 로드</button>
    </div>

    <h3>출력 결과</h3>
    <pre id="output">준비됨.</pre>
  `;

  const output = app.querySelector("#output");
  const calcBtn = app.querySelector("#btn-calc");
  const analyticsBtn = app.querySelector("#btn-analytics");

  calcBtn.addEventListener("click", async () => {
    output.textContent = "heavy-math 청크 로드 중...";
    const { runHeavyCalculation } = await import(
      /* webpackChunkName: "heavy-math" */
      "./heavy-math.js"
    );

    const startedAt = performance.now();
    const result = runHeavyCalculation(250_000);
    const elapsedMs = Math.round(performance.now() - startedAt);

    output.textContent = `무거운 계산 결과: ${result}\n소요 시간: ${elapsedMs}ms`;
  });

  analyticsBtn.addEventListener("click", async () => {
    output.textContent = "analytics 청크 로드 중...";
    const { initAnalytics, trackEvent } = await import(
      /* webpackChunkName: "analytics" */
      "./analytics.js"
    );

    initAnalytics();
    trackEvent("button_click", { buttonId: "btn-analytics" });

    output.textContent = "분석 도구가 로드되었습니다. 콘솔에서 이벤트를 확인하세요.";
  });
}

injectStyles(stylesText);
renderApp();

"무거운" 모듈을 생성합니다. 이 코드는 초기 번들에 포함시키고 싶지 않은 비용이 큰 로직(또는 대규모 라이브러리)을 시뮬레이션하기 위해 의도적으로 CPU 집약적으로 작성되었습니다.

export function runHeavyCalculation(iterations) {
  let acc = 0;

  for (let i = 0; i < iterations; i += 1) {
    // 루프가 너무 쉽게 최적화되어 사라지지 않도록 약간의 수학 연산을 추가합니다.
    acc += Math.sqrt(i ^ (i >> 3)) % 97;
  }

  return Math.round(acc);
}

분석(analytics) 모듈을 생성합니다. 실제 앱에서는 외부 SDK를 감싸는 형태일 수 있으며, 첫 화면 렌더링에 필수적이지 않은 경우가 많아 지연 로딩(lazy loading)에 아주 적합한 후보입니다.

let analyticsReady = false;

export function initAnalytics() {
  if (analyticsReady) return;

  analyticsReady = true;
  console.log("[analytics] 초기화됨");
}

export function trackEvent(name, payload) {
  if (!analyticsReady) {
    console.warn("[analytics] 초기화 전 trackEvent 호출됨; 지금 초기화합니다.");
    initAnalytics();
  }

  const event = {
    name,
    payload,
    at: new Date().toISOString(),
  };

  console.log("[analytics] 이벤트 발생", event);
}

예상 결과: 페이지를 새로고침하고 개발자 도구 → 네트워크 탭을 연 뒤 "무거운 계산 실행"을 클릭하세요. heavy-math.[hash].js와 같은 이름의 새로운 청크 요청이 나타나야 합니다. "분석 도구 로드"를 클릭하면 analytics.[hash].js를 요청하고 콘솔에 로그를 출력합니다.

⚠️ 참고: 만약 실수로 파일 상단에 import { runHeavyCalculation } from "./heavy-math.js"와 같이 작성하면, Webpack은 이를 초기 번들에 포함시켜 버려 코드 분할이 일어나지 않습니다. 동적 import는 반드시 나중에 실행되는 함수 경로 내부에서 발생해야 합니다.

구현 3: Prefetch/Preload 힌트 + 공통 코드 분할

어떤 청크들은 "지금 당장은 필요 없지만 곧 필요할 가능성이 높은" 것들입니다. Webpack은 초기 로드 후 유휴 시간(idle time)에 청크를 다운로드하는 prefetch와 같은 리소스 힌트를 지원합니다. 이를 통해 초기 JS 크기를 작게 유지하면서도 버튼 클릭 시 즉각적인 반응을 제공할 수 있습니다.

분석 도구 import에 prefetch를 추가하도록 업데이트합니다. 이렇게 하면 초기 JS 크기를 작게 유지하면서 브라우저에 대역폭 여유가 있을 때 분석 도구를 미리 가져오게 할 수 있습니다.

import stylesText from "./styles.css";

function injectStyles(cssText) {
  const style = document.createElement("style");
  style.textContent = cssText;
  document.head.append(style);
}

function renderApp() {
  const app = document.querySelector("#app");
  app.innerHTML = `
    <h2>Webpack Code Splitting Demo</h2>
    <p>개발자 도구 → 네트워크 탭을 여세요. 버튼을 클릭하면 청크가 필요할 때 다운로드됩니다.</p>

    <div>
      <button id="btn-calc">무거운 계산 실행</button>
      <button id="btn-analytics">분석 도구 로드</button>
    </div>

    <h3>출력 결과</h3>
    <pre id="output">준비됨.</pre>
  `;

  const output = app.querySelector("#output");
  const calcBtn = app.querySelector("#btn-calc");
  const analyticsBtn = app.querySelector("#btn-analytics");

  calcBtn.addEventListener("click", async () => {
    output.textContent = "heavy-math 청크 로드 중...";
    const { runHeavyCalculation } = await import(
      /* webpackChunkName: "heavy-math" */
      "./heavy-math.js"
    );

    const startedAt = performance.now();
    const result = runHeavyCalculation(250_000);
    const elapsedMs = Math.round(performance.now() - startedAt);

    output.textContent = `무거운 계산 결과: ${result}\n소요 시간: ${elapsedMs}ms`;
  });

  analyticsBtn.addEventListener("click", async () => {
    output.textContent = "analytics 청크 로드 중...";
    const { initAnalytics, trackEvent } = await import(
      /* webpackChunkName: "analytics" */
      /* webpackPrefetch: true */
      "./analytics.js"
    );

    initAnalytics();
    trackEvent("button_click", { buttonId: "btn-analytics" });

    output.textContent = "분석 도구가 로드되었습니다. 콘솔에서 이벤트를 확인하세요.";
  });
}

injectStyles(stylesText);
renderApp();

예상 결과: 새로고침 후 네트워크 탭에서 분석 청크가 "prefetch" 이니시에이터와 함께 나타나는 것을 볼 수 있습니다(브라우저에 따라 다름). 초기 페이지가 유휴 상태가 된 후 다운로드되며, "분석 도구 로드"를 클릭할 때 훨씬 빠르게 느껴질 것입니다.

⚠️ 참고: Prefetch는 힌트일 뿐 보장되지 않습니다. 브라우저는 느린 연결이나 데이터 절약 모드에서는 이를 무시할 수 있으므로, 청크가 미리 캐싱되지 않았더라도 코드는 여전히 정상적으로 작동해야 합니다.

테스트 및 검증: 초기 로드 시 JS 전송량이 줄어들었는지 확인하기

먼저 개발자 도구에서 동작을 확인합니다. 초기 페이지 로드 시에는 클릭 전까지(또는 prefetch가 실행되기 전까지) heavy-mathanalytics를 요청하지 않아야 합니다. 그 다음 프로덕션 빌드 결과를 확인합니다. dist/assets 폴더에 단일 거대 번들 대신 여러 파일이 생성된 것을 볼 수 있습니다.

npm run build
ls -R dist

예상 결과: dist 폴더에 index.html과 함께 runtime.[hash].js, main.[hash].js, 그리고 heavy-math.[hash].js, analytics.[hash].js 같은 청크 파일들이 포함되어 있습니다.

Webpack이 수행한 작업을 수치화하기 위해 나중에 webpack-bundle-analyzer 같은 도구에 넣을 수 있는 통계 파일을 생성합니다. 추가 도구 없이도 통계 파일은 CI 아티팩트로 활용하거나 시간에 따른 빌드 크기 비교에 유용합니다.

npm run analyze:stats
node -e "const s=require('./dist/stats.json'); console.log({assets: s.assets.length, chunks: s.chunks.length});"

예상 결과: 명령어 실행 시 에셋(assets)과 청크(chunks)의 개수가 출력됩니다(둘 다 1보다 커야 합니다). 청크 수가 1이라면 모든 파일을 정적으로(static) 가져왔거나 코드 분할 설정을 비활성화했을 가능성이 큽니다.

⚠️ 참고: 개발 모드에서만 테스트하면 성능을 잘못 판단할 수 있습니다. 미니피케이션(minification), 트리 쉐이킹(tree-shaking), 청크 생성 방식이 다르므로 항상 npm run build를 실행하고 프로덕션 결과물을 테스트하세요.

마무리

중요하지 않은 기능을 필요에 따라 로드되는 청크로 분할하고, 캐싱에 유리한 해시 파일명과 선택적 prefetch를 적용하여 초기 구동 성능을 개선한 Webpack 앱을 제작해 보았습니다. 다음 단계로는 페이지별 라우트 기반 분할(route-based splitting)을 추가하거나, 실제 분석 SDK를 도입하고, 번들 시각화 도구를 연결하여 성능 저하를 조기에 발견할 수 있도록 크기 제한(size budgets)을 설정해 보세요.

comparison.translatedBy

다른 AI의 평가

Grok 4
8.2
명확성
8.0
완성도
9.0
관련성
7.0
창의성
8.0
Overall
8.2

B글은 최신 ESM 방식과 예상 결과 안내, 정확한 동적 임

번역 보기
Gemini 3 Flash
7.6
명확성
8.0
완성도
9.0
관련성
6.0
창의성
8.0
Overall
7.6

기술적으로 탄탄하며, 대부분의 튜토리얼이 간과하는 최신

번역 보기
Claude Sonnet 4.6
6.8
명확성
7.0
완성도
8.0
관련성
6.0
창의성
7.0
Overall
6.8

기술적으로 탄탄하며 ESM 설정, publicPath 주의점, minSize 임계

번역 보기
DeepSeek V3.2
5.8
명확성
6.0
완성도
7.0
관련성
5.0
창의성
6.0
Overall
5.8

내용은 실용적이나 불필요한 정보(전체 CSS 파일

번역 보기