Introduction
Webpack 5 remains in 2026 the most powerful bundling tool for modern JavaScript applications, outperforming Vite for complex projects needing fine-grained customization. Unlike zero-config bundlers, Webpack provides granular control over tree-shaking, code splitting, and asset optimizations—essential for scalable apps like those with micro-frontends or PWAs.
This intermediate tutorial walks you through setting up a complete project step by step: ES6+ transpilation via Babel, CSS/Sass handling, Hot Module Replacement (HMR), and production builds with minification and chunking. You'll end up with a functional, tested setup ready for CI/CD integration. Ideal for senior developers managing monorepos or shared libraries, where flexibility trumps simplicity. By the end, your bundle will be optimized for Lighthouse 100/100 scores.
Prerequisites
- Node.js 18+ installed
- Knowledge of ES6+ JavaScript, modules (import/export)
- npm or yarn as package manager
- Code editor (VS Code recommended)
- Unix-like terminal (WSL on Windows)
Project Initialization
mkdir webpack-modern-project
cd webpack-modern-project
npm init -y
npm install --save-dev webpack@5 webpack-cli webpack-dev-server html-webpack-plugin mini-css-extract-plugin css-loader style-loader sass-loader sass babel-loader @babel/core @babel/preset-env
npm install lodashThis command initializes an npm project and installs Webpack 5 with its essential tools: CLI for scripts, dev-server for HMR, plugins for HTML/CSS, loaders for CSS/Sass/Babel, and lodash as a dependency to test imports. Avoid global versions to isolate projects.
package.json Scripts
{
"name": "webpack-modern-project",
"version": "1.0.0",
"scripts": {
"dev": "webpack serve --mode development --open",
"build": "webpack --mode production",
"build:analyze": "webpack --mode production --profile --json > stats.json"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"babel-loader": "^9.1.3",
"css-loader": "^7.0.0",
"html-webpack-plugin": "^5.6.0",
"mini-css-extract-plugin": "^2.9.0",
"sass": "^1.77.0",
"sass-loader": "^14.0.0",
"style-loader": "^4.0.0",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
},
"dependencies": {
"lodash": "^4.17.21"
}
}The scripts define dev for the development server with auto-browser opening, build for production, and build:analyze for bundle analysis via webpack-bundle-analyzer (optional). This structures workflows without redundancy.
Basic Source Files
import _ from 'lodash';
import './styles.scss';
const message = `Webpack 5 fonctionne ! Taille lodash: ${_.size({key: 'value'})}`;
document.body.innerHTML = `<h1>${message}</h1><div class="box">Boîte stylée</div>`;
console.log('Projet prêt pour HMR');This entry file imports lodash (tree-shakable), a SCSS file, and manipulates the DOM. It demonstrates multi-resource bundling. Also create src/styles.scss with .box { background: blue; padding: 20px; color: white; } to test.
Basic Webpack Configuration
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true
},
devServer: {
static: './dist',
port: 3000,
hot: true
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
title: 'Webpack 5 Moderne'
})
],
mode: 'development'
};Initial config with entry/output hashing for cache-busting, auto-cleaning of dist, and devServer for HMR on port 3000. HtmlWebpackPlugin injects the bundle into an HTML template. Create a basic src/index.html: .
First Development Test
Run npm run dev: Webpack starts a server at http://localhost:3000 with HMR enabled. Edit src/index.js or SCSS: changes apply instantly without a full reload, like a 'hot' live-reload. Check the console to confirm lodash and styles.
Adding Babel for Transpilation
{
"presets": [
["@babel/preset-env", {
"targets": {
"chrome": "58",
"firefox": "57",
"ie": "11",
"safari": "12"
},
"useBuiltIns": "usage",
"corejs": 3
}]
]
}"@babel/preset-env" transpiles ES6+ for legacy browsers, with on-demand polyfills via core-js. Targets set compatibility; useBuiltIns: 'usage' optimizes by importing only needed polyfills, avoiding a bloated bundle.
Babel and CSS Loaders in webpack.config
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
assetModuleFilename: 'assets/[hash][ext][query]',
clean: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.(scss|css)$/i,
use: [
process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}
]
},
devServer: {
static: './dist',
port: 3000,
hot: true
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
title: 'Webpack 5 Moderne'
}),
...(process.env.NODE_ENV === 'production' ? [new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})] : [])
],
mode: 'development'
};Loader rules: Babel for JS (excluding node_modules), CSS/Sass chain (style-loader for dev, MiniCssExtract for prod), asset/resource for images (optimized copy). NODE_ENV conditional separates dev/prod. Add an image in src to test.
Advanced Optimizations
Test it: Add ES2022 async code in index.js (e.g., top-level await). Babel transpiles it, HMR persists. For production, npm run build generates dist/ with extracted CSS, minified JS (via implicit TerserPlugin), and hashed assets.
Code Splitting with Dynamic Imports
import _ from 'lodash';
import './styles.scss';
const message = `Webpack 5 avec splitting ! Taille lodash: ${_.size({key: 'value'})}`;
document.body.innerHTML = `<h1>${message}</h1><div class="box">Cliquez pour lazy load</div><button id="load">Charger chunk</button>`;
const btn = document.getElementById('load');
btn.addEventListener('click', async () => {
const { lazyFunc } = await import('./lazy.js');
lazyFunc();
});
console.log('HMR prêt');Dynamic import creates a separate chunk for ./lazy.js (create it: export const lazyFunc = () => alert('Chunk chargé !');). Webpack auto-splits, with optional prefetch/preload via magic comments. Ideal for lazy-loading routes.
Final Config with Splitting and Performance
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
assetModuleFilename: 'assets/[hash][ext][query]',
clean: true
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\\/]node_modules[\\\/]/,
name: 'vendors',
chunks: 'all',
priority: 20
}
}
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.(scss|css)$/i,
use: [
process.env.NODE_ENV === 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
'sass-loader'
]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'assets/[hash][ext][query]'
}
}
]
},
devServer: {
static: './dist',
port: 3000,
hot: true,
open: true
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
title: 'Webpack 5 Moderne'
}),
...(process.env.NODE_ENV === 'production' ? [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
] : [])
],
mode: process.env.NODE_ENV || 'development'
};splitChunks optimization extracts vendors (lodash) into a shared chunk. chunkFilename for dynamic imports. CSS-loader importLoaders:1 applies Sass to @import. Performance: bundles <100kb gzipped, cache via hashes.
Best Practices
- Separate dev/prod: Use
process.env.NODE_ENVto conditionalize loaders/plugins, minimizing dev bundles. - Tree-shaking: Use named imports (e.g.,
import { debounce } from 'lodash'), and setsideEffects: falsein package.json. - Aggressive caching: Hashes on all outputs +
cache: { type: 'filesystem' }in webpack.config for 10x faster builds. - Bundle analysis: Add webpack-bundle-analyzer to visualize sizes/chunks.
- Source maps:
devtool: 'source-map'in dev,'hidden-source-map'in prod.
Common Errors to Avoid
- No node_modules exclude on babel-loader: bundle explodes (builds 10s+).
- Forget clean: true: Orphaned files pollute dist/ after rebuilds.
- Style-loader in prod: Inline CSS bloats JS; always use MiniCssExtractPlugin.
- No splitChunks: Duplicated vendors in every chunk, +200kb/app.
Next Steps
- Official docs: Webpack 5 Guide
- Advanced: Module Federation for micro-frontends
- Tools: SpeedMeasurePlugin for build profiling