Introduction
Webpack 5 introduced significant improvements including persistent caching, module federation, better tree shaking, and improved build performance. This guide covers essential configurations with modern best practices.
Essential Dependencies
Core Dependencies
| Package | Purpose |
|---|
webpack | Core bundling functionality |
webpack-cli | Command-line interface |
webpack-dev-server | Development server with HMR |
webpack-merge | Configuration merging |
HTML & CSS
| Package | Purpose |
|---|
html-webpack-plugin | Generate HTML files |
mini-css-extract-plugin | Extract CSS to separate files |
css-minimizer-webpack-plugin | CSS optimization |
postcss | CSS transformation |
postcss-loader | PostCSS integration |
postcss-preset-env | Modern CSS polyfills |
JavaScript & TypeScript
| Package | Purpose |
|---|
babel-loader | Babel integration |
@babel/core | Babel core |
@babel/preset-env | ES+ transpilation |
swc-loader | Fast Rust-based transpilation |
ts-loader | TypeScript support |
core-js | Polyfills for modern JS |
Optimization & Analysis
| Package | Purpose |
|---|
terser-webpack-plugin | JS minification |
esbuild-webpack-plugin | Ultra-fast minification |
webpack-bundle-analyzer | Bundle size analysis |
compression-webpack-plugin | Gzip/Brotli compression |
split-chunks-visualizer | Chunk visualization |
Utilities
| Package | Purpose |
|---|
copy-webpack-plugin | Copy static files |
image-minimizer-webpack-plugin | Image optimization |
eslint-webpack-plugin | Linting integration |
cross-env | Cross-platform env variables |
Keyword Meanings
| Keyword | Description | Use Case |
|---|
source-map | Full source map, generates .map file | Production (accurate errors) |
inline-source-map | Embeds map in bundle | Development |
eval | Uses eval(), no .map file | Fast development |
cheap | Only maps lines, not columns | Development |
module | Maps original source before loaders | Debugging source |
Recommended Configurations
const devtool =
process.env.NODE_ENV === "production"
? "source-map" // Production: accurate, separate file
: "eval-cheap-module-source-map"; // Development: fast, good quality
| Value | Speed | Quality | Output |
|---|
source-map | Slowest | Highest | Separate .map file |
hidden-source-map | Slow | Highest | Hidden .map file |
inline-source-map | Slow | Highest | Embedded in bundle |
eval-source-map | Medium | High | Via eval() |
eval-cheap-module-source-map | Fast | Medium | Via eval(), lines only |
eval | Fastest | Lowest | Via eval(), no mapping |
cheap-source-map | Medium | Medium | Separate file, lines only |
cheap-module-source-map | Medium | High | Original source, lines only |
Complete Webpack Configuration
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const { EsbuildPlugin } = require("esbuild-loader");
const CopyPlugin = require("copy-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const CompressionPlugin = require("compression-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
module.exports = (env, argv) => {
const isProduction = argv.mode === "production";
const isDevelopment = !isProduction;
return {
// Entry configuration
entry: {
main: "./src/index.js",
// Multiple entry points
// vendor: './src/vendor.js',
},
// Output configuration
output: {
path: path.resolve(__dirname, "dist"),
filename: isProduction ? "js/[name].[contenthash:8].js" : "js/[name].js",
chunkFilename: isProduction
? "js/[name].[contenthash:8].chunk.js"
: "js/[name].chunk.js",
assetModuleFilename: "assets/[name].[contenthash:8][ext]",
publicPath: "/",
clean: true, // Clean output directory
// Library target (if building a library)
// library: {
// type: 'umd',
// name: 'MyLibrary',
// export: 'default',
// },
// Hashing options
hashFunction: "xxhash64", // Faster hashing
// Module IDs
chunkIds: isProduction ? "deterministic" : "named",
moduleIds: isProduction ? "deterministic" : "named",
},
// Source map configuration
devtool: isProduction ? "source-map" : "eval-cheap-module-source-map",
// File resolution
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx", ".json"],
alias: {
"@": path.resolve(__dirname, "src"),
"@components": path.resolve(__dirname, "src/components"),
"@utils": path.resolve(__dirname, "src/utils"),
"@hooks": path.resolve(__dirname, "src/hooks"),
// Disable symlinks for faster resolution
// '@': path.resolve(__dirname, 'src'),
},
// Cache module resolution
cache: true,
// Exclude node_modules from symlink resolution
symlinks: false,
},
// Module rules
module: {
rules: [
// TypeScript with SWC (faster than Babel)
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: "swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript",
},
target: "es2017",
transform: {
react: {
runtime: "automatic",
},
},
},
},
},
},
// JavaScript with Babel (if not using SWC)
// {
// test: /\.jsx?$/,
// exclude: /node_modules/,
// use: {
// loader: 'babel-loader',
// options: {
// cacheDirectory: true,
// cacheCompression: false,
// },
// },
// },
// CSS with PostCSS
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : "style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1,
},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
stage: 2,
autoprefixer: { grid: true },
},
],
],
},
},
},
],
},
// SCSS/SASS
// {
// test: /\.s[ac]ss$/,
// use: [
// isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
// 'css-loader',
// 'postcss-loader',
// 'sass-loader',
// ],
// },
// Asset modules (images, fonts, etc.)
{
test: /\.(png|svg|jpg|jpeg|gif|webp)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb
},
},
generator: {
filename: "images/[name].[hash:8][ext]",
},
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
generator: {
filename: "fonts/[name].[hash:8][ext]",
},
},
// Audio/Video
{
test: /\.(mp3|mp4|webm|ogg|wav)$/i,
type: "asset/resource",
generator: {
filename: "media/[name].[hash:8][ext]",
},
},
// CSV/XML/JSON data files
{
test: /\.(csv|xml|json)$/i,
type: "asset/resource",
generator: {
filename: "data/[name].[hash:8][ext]",
},
},
// Raw files
{
test: /\.txt$/,
type: "asset/source",
},
],
},
// Plugins
plugins: [
// Generate HTML
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
inject: "body",
minify: isProduction
? {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
: false,
// Template parameters
templateParameters: {
title: "My Application",
lang: "en",
},
}),
// Extract CSS in production
isProduction &&
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css",
chunkFilename: "css/[name].[contenthash:8].chunk.css",
// Module filename for extracted CSS
moduleFilename: module => {
return `css/${module.name}.[contenthash:8].css`;
},
}),
// Copy static files
new CopyPlugin({
patterns: [
{
from: "public",
to: ".",
noErrorOnMissing: true,
globOptions: {
ignore: ["**/.gitkeep"],
},
},
],
}),
// Define environment variables
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(
process.env.NODE_ENV || (isProduction ? "production" : "development")
),
"process.env.API_URL": JSON.stringify(
process.env.API_URL || "http://localhost:3000"
),
}),
// ESLint (development only)
isDevelopment &&
new ESLintPlugin({
extensions: ["js", "jsx", "ts", "tsx"],
exclude: "node_modules",
fix: true,
}),
// Bundle analyzer (development only)
isDevelopment &&
new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "../bundle-report.html",
openAnalyzer: false,
statsFilename: "../bundle-stats.json",
}),
// Compression (production only)
isProduction &&
new CompressionPlugin({
filename: "[path][base].gz",
algorithm: "gzip",
test: /\.(js|css|html|svg)$/,
threshold: 8192, // Only compress > 8kb
minRatio: 0.8,
}),
// Brotli compression (production only)
isProduction &&
new CompressionPlugin({
filename: "[path][base].br",
algorithm: "brotliCompress",
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
}),
].filter(Boolean),
// Development server
devServer: {
static: {
directory: path.join(__dirname, "public"),
},
hot: true,
port: process.env.PORT || 3000,
open: true,
compress: true,
// API proxy
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
pathRewrite: { "^/api": "" },
},
},
// History API fallback
historyApiFallback: true,
// Client overlay for errors
client: {
overlay: {
errors: true,
warnings: false,
runtimeErrors: true,
},
progress: true,
},
// Live reload
liveReload: true,
// Allowed hosts
allowedHosts: "all",
// CORS headers
headers: {
"Access-Control-Allow-Origin": "*",
},
},
// Optimization
optimization: {
// Minimize in production
minimize: isProduction,
// Minimizer plugins
minimizer: [
// ESBuild (faster than Terser)
new EsbuildPlugin({
target: "es2017",
css: true,
minify: isProduction,
}),
// Fallback to Terser
// new TerserPlugin({
// terserOptions: {
// parse: { ecma: 2017 },
// compress: {
// ecma: 2017,
// comparisons: false,
// inline: 2,
// drop_console: isProduction,
// pure_funcs: isProduction ? ['console.log'] : [],
// },
// mangle: { safari10: true },
// output: {
// ecma: 2017,
// comments: false,
// ascii_only: true,
// },
// },
// parallel: true,
// extractComments: false,
// }),
// CSS minimizer
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
"default",
{
discardComments: { removeAll: true },
minifyFontValues: { removeQuotes: false },
},
],
},
}),
],
// Split chunks configuration
splitChunks: {
chunks: "all",
maxInitialRequests: 25,
maxAsyncRequests: 25,
minSize: 20000,
maxSize: 244000,
cacheGroups: {
// Default vendor chunk
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: "vendors",
},
// React ecosystem
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)[\\/]/,
name: "react-vendor",
chunks: "all",
priority: 20,
},
// Large libraries
largeVendor: {
test: /[\\/]node_modules[\\/].*(lodash|moment|date-fns|axios)[\\/]/,
name: "utils-vendor",
chunks: "all",
priority: 10,
},
// Common chunks (shared between entry points)
common: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
// Styles
styles: {
name: "styles",
type: "css/mini-extract",
chunks: "all",
enforce: true,
},
},
},
// Separate runtime chunk
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`,
},
// Consistent module and chunk IDs
moduleIds: isProduction ? "deterministic" : "named",
chunkIds: isProduction ? "deterministic" : "named",
// Merge duplicate chunks
mergeDuplicateChunks: true,
// Split modules across chunks
splitModuleChunks: true,
// CSS ordering
cssProcessorOptions: {
parser: true,
map: {
inline: false,
annotation: true,
},
},
// Source map for minimizer
minimizerOpts: {
sourceMap: true,
},
// Used exports (tree shaking)
usedExports: true,
sideEffects: true,
// Concatenate modules where possible
concatenateModules: true,
},
// Performance hints
performance: {
hints: isProduction ? "warning" : false,
maxEntrypointSize: 512000, // 500kb
maxAssetSize: 512000,
assetFilter: assetFilename => {
return !assetFilename.endsWith(".map");
},
},
// Stats configuration
stats: {
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
errors: true,
errorDetails: true,
warnings: true,
entrypoints: "auto",
env: true,
hash: false,
},
// Caching configuration (Webpack 5 feature)
cache: {
type: isProduction ? "filesystem" : "memory",
buildDependencies: {
config: [__filename],
},
cacheDirectory: path.resolve(__dirname, ".webpack-cache"),
compression: "gzip",
hashAlgorithm: "xxhash64",
},
// Experiments
experiments: {
// Top-level await
topLevelAwait: true,
// Asset modules
asset: true,
// WebAssembly modules
wasm: true,
// Async WebAssembly modules
asyncWebAssembly: true,
// CSS modules
css: true,
// FutureDefaults
futureDefaults: true,
},
// External dependencies (for library building)
// externals: {
// react: 'React',
// 'react-dom': 'ReactDOM',
// },
// Devtool fallback
// snapshot: {
// managedPaths: [path.resolve(__dirname, 'node_modules')],
// immutablePaths: [],
// },
// Ignore warnings
ignoreWarnings: [
/Failed to parse source map/,
/Critical dependency: the request of a dependency is an expression/,
],
// Infrastructure logging
infrastructureLogging: {
level: "info",
console: {
InfoPlugin: true,
},
},
};
};
Environment-Based Configuration
Environment Variables
# .env.development
NODE_ENV=development
API_URL=http://localhost:8080
PORT=3000
# .env.production
NODE_ENV=production
API_URL=https://api.example.com
Using dotenv
// Load environment variables
require("dotenv").config({
path:
process.env.NODE_ENV === "production"
? ".env.production"
: ".env.development",
});
Advanced Features
Module Federation (Micro-frontends)
// Host application
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "host",
remotes: {
remoteApp: "remoteApp@http://localhost:3001/remoteEntry.js",
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
},
}),
],
};
// Remote application
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "remoteApp",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/Button",
"./Header": "./src/Header",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
},
}),
],
};
React Fast Refresh
// webpack.config.js
module.exports = {
plugins: [
new ReactRefreshWebpackPlugin({
overlay: {
sockPort: 3000,
useLegacyWDSSockets: false,
},
}),
],
// Disable Fast Refresh for production
!isProduction && new ReactRefreshWebpackPlugin(),
};
Bundle Optimization Strategies
1. Code Splitting
// Dynamic imports
const handleClick = () => {
import(/* webpackChunkName: "analytics" */ "./analytics")
.then(module => module.trackClick())
.catch(err => console.error(err));
};
// Prefetch hints
import(/* webpackPrefetch: true */ "./PrefetchComponent");
import(/* webpackPreload: true */ "./PreloadComponent");
2. Tree Shaking
// package.json
{
"sideEffects": ["*.css", "*.scss"]
}
3. Bundle Analysis
// Visualizer plugin
new BundleAnalyzerPlugin({
analyzerMode: "static",
openAnalyzer: false,
reportFilename: "bundle-report.html",
});
// Or use webpack-bundle-analyzer CLI
// npx webpack-bundle-analyzer dist/stats.json
Development Workflow
Common Scripts (package.json)
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"build:analyze": "webpack --mode production --env analyze",
"serve": "webpack serve --mode production",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist .webpack-cache"
}
}
Common Issues & Solutions
| Issue | Solution |
|---|
| Slow build | Enable cache: { type: 'filesystem' } |
| Large bundle | Use splitChunks and analyze with bundle analyzer |
| Source maps broken | Use source-map devtool in production |
| HMR not working | Ensure hot: true in devServer |
| Memory issues | Use spawn: false option for fork-ts-checker |
| Module not found | Check resolve.alias and extensions |
Summary
Key Takeaways
- Use Webpack 5 features: Persistent caching, asset modules, module federation
- Choose appropriate devtool:
source-map for production, eval-cheap-module-source-map for development
- Optimize with splitChunks: Separate vendor and common code
- Enable filesystem caching: Dramatically faster rebuilds
- Use modern minimizers: ESBuild or SWC for faster builds
- Configure resolve properly: Extensions, alias, cache settings
- Enable tree shaking: Use
sideEffects in package.json
- Monitor bundle size: Use bundle analyzer regularly