Webpack is the most popular JavaScript module bundler. This article explores its core concepts and configuration methods.
Core Concepts
Basic Architecture
Webpack Core Concepts:
┌─────────────────────────────────────────────────────┐
│ │
│ Entry │
│ └── Starting point for bundling │
│ │
│ Output │
│ └── Location and naming of bundled files │
│ │
│ Loaders │
│ └── Transform non-JS files (CSS, images, etc.) │
│ │
│ Plugins │
│ └── Extend functionality (compression, etc.) │
│ │
│ Mode │
│ └── development / production / none │
│ │
└─────────────────────────────────────────────────────┘
Basic Configuration
// webpack.config.js
const path = require('path');
module.exports = {
// Mode
mode: 'development',
// Entry
entry: './src/index.js',
// Output
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
clean: true // Clean dist on each build
},
// Module rules
module: {
rules: []
},
// Plugins
plugins: [],
// Dev server
devServer: {
static: './dist',
port: 3000,
hot: true
}
};
Loaders Configuration
Processing JavaScript
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: '> 0.25%, not dead',
useBuiltIns: 'usage',
corejs: 3
}]
]
}
}
}
]
}
};
Processing TypeScript
module.exports = {
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
};
Processing CSS
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
// Development - style-loader injects into DOM
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// Production - extract to separate file
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
// Sass/SCSS
{
test: /\.s[ac]ss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
// CSS Modules
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles/[name].[contenthash].css'
})
]
};
Processing Assets
module.exports = {
module: {
rules: [
// Images
{
test: /\.(png|jpg|jpeg|gif|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // Under 8KB to base64
}
},
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]'
}
}
]
}
};
Common Plugins
HTML Generation
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
inject: 'body',
minify: {
removeComments: true,
collapseWhitespace: true
}
})
]
};
Environment Variables
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.API_URL': JSON.stringify('https://api.example.com'),
__DEV__: JSON.stringify(false)
})
]
};
Copy Static Assets
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{ from: 'public', to: '' },
{ from: 'src/assets', to: 'assets' }
]
})
]
};
Code Splitting
Entry Splitting
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js',
admin: './src/admin/index.js'
},
output: {
filename: '[name].[contenthash].js'
}
};
Dynamic Imports
// Automatic code splitting
button.addEventListener('click', async () => {
const module = await import(
/* webpackChunkName: "heavy-module" */
'./heavy-module.js'
);
module.doSomething();
});
// Prefetch
import(
/* webpackPrefetch: true */
'./analytics.js'
);
// Preload
import(
/* webpackPreload: true */
'./critical-module.js'
);
SplitChunks Optimization
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
cacheGroups: {
// Third-party libraries
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
// React related
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
chunks: 'all',
priority: 20
},
// Common code
common: {
minChunks: 2,
name: 'common',
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
}
};
Dev Server
DevServer Configuration
module.exports = {
devServer: {
static: {
directory: path.join(__dirname, 'public')
},
port: 3000,
hot: true,
open: true,
compress: true,
historyApiFallback: true,
// Proxy configuration
proxy: [
{
context: ['/api'],
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
],
// Custom middleware
setupMiddlewares: (middlewares, devServer) => {
devServer.app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
return middlewares;
}
}
};
Source Maps
module.exports = {
// Development - fast rebuild, accurate line numbers
devtool: 'eval-cheap-module-source-map',
// Production - full mapping
// devtool: 'source-map',
// Production - hidden source
// devtool: 'hidden-source-map',
};
Production Optimization
Compression Configuration
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,
drop_debugger: true
},
format: {
comments: false
}
},
extractComments: false
}),
new CssMinimizerPlugin()
]
}
};
Tree Shaking
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
sideEffects: true
}
};
// package.json
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
}
Cache Optimization
module.exports = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js'
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
Multi-Environment Config
Configuration Merging
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
// webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
output: {
filename: '[name].js'
},
devServer: {
port: 3000,
hot: true
}
});
// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
output: {
filename: '[name].[contenthash].js'
}
});
Best Practices Summary
Webpack Optimization Strategies:
┌─────────────────────────────────────────────────────┐
│ │
│ Build Speed │
│ ├── Narrow loader scope │
│ ├── Use persistent caching │
│ ├── Multi-process parallel builds │
│ └── Use alias and extensions wisely │
│ │
│ Output Optimization │
│ ├── Code splitting + lazy loading │
│ ├── Tree shaking removes dead code │
│ ├── Compress JS/CSS/images │
│ └── Use contenthash appropriately │
│ │
│ Developer Experience │
│ ├── Hot Module Replacement (HMR) │
│ ├── Source maps configuration │
│ └── Separate dev/prod environments │
│ │
└─────────────────────────────────────────────────────┘
| Concept | Purpose |
|---|---|
| Entry | Define bundling entry point |
| Output | Define output configuration |
| Loaders | Transform non-JS resources |
| Plugins | Extend build functionality |
| SplitChunks | Code splitting optimization |
Understanding Webpack is mastering the core of modern frontend engineering.