Webpack 5는 대규모 프로덕션 애플리케이션의 지배적인 번들러로 남아 있습니다. 이 가이드는 기본 로더부터 번들 크기를 40-60% 줄이는 고급 최적화 기술까지 다룹니다.
Webpack 5 기초 및 Entry/Output
webpack.config.js 파일은 모든 번들링 동작을 제어합니다.
// webpack.config.js — Complete Production Config
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDev ? 'development' : 'production',
entry: {
main: './src/index.tsx',
// Multiple entry points for different pages
// admin: './src/admin/index.tsx',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isDev ? '[name].js' : '[name].[contenthash:8].js',
chunkFilename: isDev ? '[name].chunk.js' : '[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash:8][ext][query]',
clean: true, // Remove old files on each build
publicPath: '/',
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
},
},
// Persistent filesystem cache — huge speed improvement for incremental builds
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename], // Invalidate cache when config changes
},
},
devServer: {
port: 3000,
hot: true,
historyApiFallback: true,
compress: true,
},
};필수 Loaders
로더는 파일이 의존성 그래프에 추가되기 전에 변환합니다.
// Essential Webpack Loaders Configuration
module.exports = {
module: {
rules: [
// 1. TypeScript/JavaScript with Babel
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: '>0.25%, not dead' }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
// Cache Babel results for faster rebuilds
cacheDirectory: true,
cacheCompression: false,
},
},
},
// 2. CSS / SCSS / PostCSS
{
test: /\.module\.(css|scss)$/, // CSS Modules
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { modules: true, importLoaders: 2 },
},
'postcss-loader',
'sass-loader',
],
},
{
test: /(?!\.module)\.(css|scss)$/, // Global CSS
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
// 3. Images and fonts (Webpack 5 Asset Modules — no loader needed)
{
test: /\.(png|jpg|jpeg|gif|webp)$/i,
type: 'asset', // Auto-chooses between inline and resource
parser: {
dataUrlCondition: { maxSize: 10 * 1024 }, // Inline if < 10KB
},
},
{
test: /\.svg$/,
use: ['@svgr/webpack'], // Import SVGs as React components
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
};코드 분할 및 레이지 로딩
코드 분할은 Webpack의 가장 중요한 최적화 기능입니다.
// Code Splitting and Lazy Loading
// 1. Dynamic imports (automatic code splitting)
// React.lazy creates a separate chunk for each lazy component
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
// 2. Manual chunk naming with magic comments
const HeavyChart = lazy(
() => import(/* webpackChunkName: "charts" */ './components/HeavyChart')
);
// 3. Webpack SplitChunksPlugin configuration
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// Vendor chunk: node_modules
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 20,
},
// Separate React from other vendors
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'react-vendor',
chunks: 'all',
priority: 30,
},
// Shared chunks used by 2+ modules
common: {
name: 'common',
minChunks: 2,
priority: 10,
reuseExistingChunk: true,
},
},
},
runtimeChunk: 'single', // Separate runtime chunk
},
};프로덕션 최적화
프로덕션 빌드는 번들 크기를 최소화하기 위한 적극적인 최적화가 필요합니다.
// Production Optimization Configuration
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // Use all CPU cores
terserOptions: {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true,
pure_funcs: ['console.log'],
},
format: { comments: false },
},
}),
new CssMinimizerPlugin(),
],
// Tree shaking — only include used exports
usedExports: true,
sideEffects: true, // Respect package.json sideEffects field
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
}),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
},
}),
// Only add analyzer when needed: ANALYZE=true npm run build
process.env.ANALYZE && new BundleAnalyzerPlugin(),
].filter(Boolean),
};자주 묻는 질문
언제 Webpack 대신 Vite를 사용해야 하나요?
Module Federation, 복잡한 기존 Webpack 설정, IE11 지원이 필요한 경우 Webpack을 선택하세요.
Webpack 번들 크기를 어떻게 분석하나요?
webpack-bundle-analyzer를 사용하여 번들 크기를 소비하는 것을 인터랙티브하게 시각화하세요.
Webpack 5의 Module Federation이란?
Module Federation은 별도의 빌드 프로세스 없이 여러 Webpack 빌드가 런타임에 코드를 공유할 수 있게 합니다.
Webpack 빌드가 왜 이렇게 느린가요?
일반적인 원인: 영구 캐싱 없음, 너무 많은 로더, node_modules 제외 없는 babel-loader.