본문으로 건너뛰기
Previous
Next
Webpack 완벽 가이드 | 내부 동작·Loaders·Plugins·분할·프로덕션 패턴

Webpack 완벽 가이드 | 내부 동작·Loaders·Plugins·분할·프로덕션 패턴

Webpack 완벽 가이드 | 내부 동작·Loaders·Plugins·분할·프로덕션 패턴

이 글의 핵심

Webpack 완벽 가이드에 대해 정리한 개발 블로그 글입니다. Webpack 5로 모듈 번들링을 구현·최적화하는 가이드입니다. 엔트리·출력·로더·플러그인 실습뿐 아니라, 모듈 의존성 그래프가 어떻게 만들어지고 청크로 나뉘는지, 로더 체인과 Tapable 플러그인 훅, 코드… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드: Webpack,…

이 글의 핵심

Webpack 5로 모듈 번들링을 구현·최적화하는 가이드입니다. 엔트리·출력·로더·플러그인 실습뿐 아니라, 모듈 의존성 그래프가 어떻게 만들어지고 청크로 나뉘는지, 로더 체인과 Tapable 플러그인 훅, 코드 스플리팅·Tree Shaking·프로덕션 캐싱 패턴까지 내부 동작을 함께 정리했습니다.

실무 경험 공유: Webpack 설정을 정리하면서 빌드 시간을 크게 줄이고, splitChunkscontenthash로 캐시 적중률을 높인 사례는 면접에서도 설명하기 좋은 성과 지표로 쓸 수 있습니다.

들어가며: “빌드가 너무 느려요”

실무 문제 시나리오

시나리오 1: 빌드 시간이 10분이에요

의존 그래프 전체를 매번 처음부터 다시 처리하고 있을 수 있습니다. Webpack 5 파일시스템 캐시(cache: { type: 'filesystem' })와 설정 분리로 반복 빌드를 줄입니다.

시나리오 2: 번들 크기가 너무 커요

동기 import만 쓰면 한 청크에 모두 들어갑니다. 동적 import()splitChunks로 경로·벤더 단위로 나눕니다.

시나리오 3: 설정이 복잡해요

로더는 파일 단위 변환, 플러그인은 컴파일 전체 훅이라는 역할만 구분해도 구조가 잡힙니다.


1. Webpack이 한 일의 전체 그림

엔트리 모듈
  → import/require를 따라 모듈 의존성 그래프 구성
  → 규칙별 로더로 변환(TS, CSS, 에셋…)
  → 플러그인이 컴파일 훅에 연결되어 최적화·HTML 생성·환경 주입
  → 청크·에셋을 output 경로에 출력
개념역할
Entry의존성 그래프 탐색의 시작 모듈
Output내보낼 파일명·publicPath
Loader모듈 단위 변환(체인 가능)
PluginTapable 훅으로 컴파일 전체에 개입
Mode개발/프로덕션 기본값(압축, Tree Shaking, devtool 등)
Chunk출력 단위로 묶인 모듈 묶음(스플리팅 결과)

2. 의존성 그래프와 번들링 알고리즘

2.1 엔트리에서 모듈 그래프까지

Webpack은 폴더 순서로 파일을 이어 붙이지 않습니다. 각 엔트리에서 시작해 정적 분석으로 import / import() / require()(및 Webpack 전용 API)를 따라 모듈을 재귀적으로 수집합니다. 그 결과가 방향 그래프입니다: 노드는 모듈, 간선은 의존 관계입니다.

  • 해석(resolve): ./foo, lodash, url?resource 같은 요청 문자열이 실제 디스크의 어떤 파일이 될지는 resolve.alias, extensions, conditionNames 등이 결정합니다.
  • 파싱: 파서가 의존성 목록을 기록합니다. import()는 단순한 동기 간선이 아니라 비동기 의존성(나중에 로드할 청크 경계)을 만듭니다.

순환 참조도 허용되며, 컴파일당 각 모듈은 일반적으로 한 번 구축됩니다(HMR·컨텍스트 모듈 등 예외는 별도).

2.2 컴파일 단계(요약)

실제 구현은 훅으로 세분화되지만, 개념적으로는 다음을 거칩니다.

  1. Make — 엔트리에서 그래프를 확장하고, 로더를 적용하며 의존성을 연결합니다.
  2. 모듈 마무리 — 의존성이 해소된 뒤 모듈 단위 후처리가 일어납니다.
  3. Seal청크 그래프를 구성합니다(엔트리·비동기 분할점·splitChunks 규칙 반영).
  4. Emit — 청크를 JS/CSS 등 자산으로 렌더링하고 디스크에 씁니다.

플러그인은 Compilation.hooks.optimizeModules, optimizeChunks, processAssets 같은 훅에 붙어 단계 사이에서 동작합니다.

2.3 모듈 그래프와 청크 그래프

  • 모듈 그래프: 어떤 소스 파일이 어떤 파일에 의존하는지.
  • 청크 그래프: 그 모듈들을 어떤 출력 파일 묶음으로 나눌지. 엔트리에서 비동기 경계 없이 도달 가능한 부분은 엔트리 청크에 가깝게 묶이고, import() 너머는 비동기 청크가 됩니다.

코드 스플리팅은 “비동기 경계와 splitChunks 규칙으로 모듈 그래프를 잘라 청크로 만든다”는 관점으로 이해하면 이후 설정이 한결 명확해집니다.


3. 로더·플러그인 아키텍처

3.1 로더: 파이프라인과 실행 순서

로더는 한 리소스(파일)씩 문자열·버퍼로 변환하는 함수(또는 비동기 함수)입니다. use: ['a', 'b', 'c']일 때 일반(normal) 단계 실행 순서는 cba(배열의 오른쪽부터)입니다.

Pitch 단계: 로더가 pitch 함수를 노출하면, 일반 단계보다 먼저 왼쪽→오른쪽으로 실행됩니다. 여기서 체인을 중단할 수 있어, 일부 로더가 불필요한 하위 로더 실행을 건너뛰게 할 수 있습니다.

로더는 Node 환경에서 this.async() 등 제한된 컨텍스트로 동작합니다. 전역 상태에 의존하지 말고, 반복 빌드 간 상태는 Webpack 5 캐시에 맡기는 편이 안전합니다.

3.2 플러그인: Tapable과 훅

Webpack 코어는 Tapable 기반 훅 시스템을 씁니다. Compiler(한 번의 실행에 대응)와 Compilation(한 번의 컴파일)이 각각 훅을 노출합니다.

  • Compiler.hooks.thisCompilation 등으로 Compilation에 서브 플러그인을 붙입니다.
  • Compilation.hooks.processAssets(Webpack 5)에서 에셋 생성·변형이 일어납니다.

정리: 파일 단위 변환이면 로더, 그래프·청크·에셋 전체에 손대야 하면 플러그인입니다.


4. 코드 스플리팅 전략

전략동작쓰임
멀티 엔트리entry 여러 개레거시 MPA, 관리자/사용자 셸 분리
동적 import()비동기 청크라우트·모달 지연 로딩
splitChunks공유 모듈 추출벤더·공통 UI 분리
runtimeChunk: 'single'런타임 분리앱 코드 해시만 바뀌도록 캐시 안정화

4.1 splitChunks 이해하기

optimization.splitChunks는 모듈 그래프가 준비된 이후에 동작합니다. chunks, cacheGroupstest·priority·minChunks 등으로 청크를 합치거나 쪼갭니다. priority가 높은 규칙이 먼저 적용되고, reuseExistingChunk로 이미 만든 청크를 재사용해 중복을 줄일 수 있습니다.

4.2 매직 코멘트

// 청크 이름(파일명 디버깅에 유리)
const Chart = React.lazy(() =>
  import(/* webpackChunkName: "chart" */ './Chart')
);

// 여유 시점에 미리 가져오기(다음 화면 가능성)
import(/* webpackPrefetch: true */ './HeavyDialog');

// 당장 필요할 때 병렬 로드 힌트
import(/* webpackPreload: true */ './CriticalSubfeature');

출력 파일명은 여전히 output.chunkFilename[contenthash] 규칙을 따릅니다.


5. Tree Shaking 메커니즘

Tree Shaking은 ES 모듈의 정적 구조를 바탕으로 한 도달하지 않는 export 제거입니다.

  1. import { x } from './lib'처럼 정적 import이면 x만 쓰인다는 정보를 알기 쉽습니다.
  2. optimization.usedExports로 참조된 export가 표시되고(프로덕션 모드에서 공격적으로),
  3. Terser 등 미니파이어가 사용되지 않은 함수·export 본문을 제거합니다.

조건

  • 애플리케이션 코드는 import/export를 우선합니다. require·module.exports는 분석이 어렵습니다.
  • package.jsonsideEffects를 정확히 써야 합니다. false는 “이 패키지의 파일은 import 부수효과가 없다”는 뜻이며, CSS·폴리필 등 부수효과가 있는 파일은 배열로 명시합니다.
{
  "sideEffects": ["*.css", "./src/polyfills.ts"]
}

객체 하나에 메서드를 몽땅 담아 export default하는 형태, 배럴 파일로 패키지 전체를 재수출하는 패턴은 Tree Shaking을 막기 쉽습니다. moment 대신 date-fns처럼 모듈 단위로 가져올 수 있는 라이브러리를 고려하세요.


6. 프로덕션 Webpack 패턴

  • filename / chunkFilename[contenthash] — 내용이 바뀔 때만 파일명이 바뀌어 브라우저 캐시와 잘 맞습니다.
  • runtimeChunk: 'single' — Webpack 런타임 조각을 분리해 앱 코드만 바뀔 때 불필요한 캐시 무효를 줄입니다.
  • splitChunks — 여러 엔트리·비동기 청크 간 중복 모듈을 묶습니다.
  • optimization.minimize — Terser와 스코프 분석으로 용량을 줄입니다.
  • cache: { type: 'filesystem' } — 로컬·CI에서 증분 빌드 시간을 단축합니다. buildDependencies에 설정 파일을 넣어 설정 변경 시 캐시를 무효화합니다.
  • devtool: 'source-map' — 배포 시 소스맵은 별도 업로드·비공개 정책과 함께 hidden-source-map도 검토합니다.

최적화 전에 번들 분석기로 상위 용량 요인(중복 패키지, 아이콘 풀 패키지, locale 전체 포함 등)을 먼저 확인하는 것이 효율적입니다.


7. 설치 및 기본 설정

설치

npm install -D webpack webpack-cli webpack-dev-server

webpack.config.js

const path = require('path');
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  devServer: {
    static: './dist',
    port: 3000,
    hot: true,
  },
};

package.json

{
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack serve --mode development"
  }
}

8. Loaders

CSS Loader

npm install -D style-loader css-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

TypeScript Loader

npm install -D ts-loader typescript
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
};

Image Loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
    ],
  },
};

9. Plugins

HtmlWebpackPlugin

npm install -D html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      title: 'My App',
    }),
  ],
};

MiniCssExtractPlugin

npm install -D mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
  ],
};

10. Code Splitting (설정 예시)

Entry Points

module.exports = {
  entry: {
    main: './src/index.js',
    vendor: './src/vendor.js',
  },
  output: {
    filename: '[name].[contenthash].js',
  },
};

Dynamic Import

// src/index.js
button.addEventListener('click', async () => {
  const module = await import('./heavy-module.js');
  module.doSomething();
});

SplitChunksPlugin

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
        },
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

11. 최적화 (프로덕션)

const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
  mode: 'production',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
      new CssMinimizerPlugin(),
    ],
  },
};

Tree Shaking 설정 예시

// package.json
{
  "sideEffects": false
}
// webpack.config.js
module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true,
  },
};

12. Source Maps

module.exports = {
  devtool: 'source-map', // Production
  // devtool: 'eval-source-map', // Development
};

13. 환경 변수

npm install -D dotenv-webpack
const Dotenv = require('dotenv-webpack');
module.exports = {
  plugins: [
    new Dotenv(),
  ],
};

14. React 통합

npm install -D babel-loader @babel/core @babel/preset-react @babel/preset-env
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'],
          },
        },
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

15. 실전 예제: 풀 설정

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env, argv) => {
  const isDevelopment = argv.mode === 'development';
  return {
    mode: argv.mode,
    entry: './src/index.tsx',
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
        {
          test: /\.css$/i,
          use: [
            isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
            'css-loader',
          ],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i,
          type: 'asset/resource',
        },
      ],
    },
    resolve: {
      extensions: ['.tsx', '.ts', '.js'],
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
      }),
      new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
      }),
    ],
    optimization: {
      minimize: !isDevelopment,
      minimizer: [new TerserPlugin()],
      splitChunks: {
        chunks: 'all',
      },
    },
    devServer: {
      static: './dist',
      port: 3000,
      hot: true,
    },
    devtool: isDevelopment ? 'eval-source-map' : 'source-map',
  };
};

취업·면접과 연결하기

번들 최적화·코드 스플리팅·로더 체인·의존성 그래프와 청크의 관계는 프론트엔드 심화 면접에서 자주 이어집니다. 기술 면접 완벽 대비 가이드와 성과를 숫자로 쓰는 방법은 개발자 이력서·서류·면접 가이드를 참고하세요.


정리 및 체크리스트

핵심 요약

  • Webpack: 엔트리에서 모듈 그래프를 만들고 청크로 출력하는 번들러
  • 의존성 그래프 / 청크 그래프: 스플리팅은 그래프를 자르는 문제
  • Loaders: 파일 단위 변환 체인(pitch·normal 순서 이해)
  • Plugins: Tapable 훅으로 컴파일 전체 제어
  • Code Splitting: 동적 import + splitChunks + runtimeChunk
  • Tree Shaking: ESM + sideEffects + 미니파이
  • 프로덕션: contenthash, 캐시, 분석 후 최적화

구현 체크리스트

  • Webpack 설치
  • 기본 entry/output·mode
  • Loaders 규칙 정리
  • Plugins(Html, CSS 추출 등)
  • 동적 import·splitChunks 설계
  • sideEffects·프로덕션 최적화
  • Source Maps 정책
  • 파일시스템 캐시(반복 빌드)
  • React/Vue 등 프레임워크 통합

같이 보면 좋은 글


이 글에서 다루는 키워드

Webpack, Bundler, Build Tools, Optimization, JavaScript, TypeScript, Frontend, 의존성 그래프, Tree Shaking, 코드 스플리팅


자주 묻는 질문 (FAQ)

Q. Vite와 비교하면 어떤가요?

A. Webpack은 생태계·레거시·Module Federation 등 무거운 통합에 강합니다. Vite는 개발 서버·HMR이 훨씬 빠른 경우가 많아 신규 SPA에는 자주 선택됩니다. 팀 표준이나 기존 설정이 Webpack이면 내부 동작을 이해한 채로 점진 개선하는 편이 안전합니다.

Q. 학습 곡선이 높나요?

A. 옵션이 많아 초반에 부담이 있지만, 로더=파일 / 플러그인=전체, 모듈 그래프→청크만 잡으면 나머지는 패턴 축적입니다.

Q. CRA 없이 React를 설정할 수 있나요?

A. 가능합니다. babel-loader 또는 ts-loader, HtmlWebpackPlugin, 필요 시 splitChunks만 맞추면 됩니다.

Q. 프로덕션에서 Webpack을 써도 되나요?

A. 네. 대규모 제품·엔터프라이즈에서 여전히 널리 쓰이며, 캐시·분할·분석 도구와 함께 쓰면 안정적으로 운영할 수 있습니다.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Webpack 완벽 가이드 | 내부 동작·Loaders·Plugins·분할·프로덕션 패턴」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Webpack 완벽 가이드 | 내부 동작·Loaders·Plugins·분할·프로덕션 패턴」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.