Introduction
Webpack is the go-to bundler for modern web applications, capable of transforming ES6+ modules, CSS, images, and fonts into optimized bundles. In 2026, with Webpack 5, you get native features like automatic code splitting, persistent caching, and better asset management. This intermediate tutorial guides you step by step to set up a complete project: from basic bundling to production optimizations with Hot Module Replacement (HMR).
Why Webpack? Unlike Vite (faster for dev but less flexible), Webpack excels in complex builds with many loaders/plugins. Think of it as an assembly line: each loader processes a file type (JS to ES5, Sass to CSS), plugins inject HTML or clean folders. At the end, a pro bookmarks this guide for recurring setups. Ready to bundle like a pro?
Prerequisites
- Node.js 20+ installed
- Basic knowledge of ES6+ JavaScript and modules
- An editor like VS Code
- Git for version control
Initialization and Installation
mkdir webpack-tutorial && cd webpack-tutorial
npm init -y
npm install --save-dev webpack 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 clean-webpack-plugin
npm install lodashThis command initializes an npm project and installs Webpack 5, CLI, dev server, essential plugins (HTML, CSS extraction, cleaning), and loaders (Babel for modern JS, Sass/CSS). Lodash is an external module to bundle. Avoid global installs to isolate versions per project.
Basic File Structure
Create manually:
src/index.js: JS entry pointsrc/style.scss: Sass filepublic/index.html: HTML template (optional, handled by plugin)
src/
index.js
style.scss
public/
index.html
webpack.config.js
package.json
Webpack starts from src/index.js (entry) to generate dist/main.js (output).
Basic Webpack Configuration
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
mode: 'development',
devServer: {
static: './dist',
port: 3000,
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin(),
],
module: {
rules: [
{
test: /\.scss$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
};This basic config sets the JS entry, cleaned output in /dist, dev mode with server on port 3000. HtmlWebpackPlugin injects the bundle into HTML; MiniCssExtractPlugin separates CSS into a distinct file. Added a basic Sass rule. Test with npx webpack serve – avoid mode: 'none' without manual optimizations.
JavaScript Entry Point with Modules
import _ from 'lodash';
import './style.scss';
const greet = () => {
const element = document.createElement('div');
element.innerHTML = _.join(['Webpack', 'est', 'super!'], ' ');
element.classList.add('hello');
return element;
};
document.body.appendChild(greet());
console.log('Webpack chargé!');This entry file imports Lodash (bundled), SCSS, and creates a dynamic DOM element. It demonstrates tree-shaking (Webpack removes unused Lodash code). Add console.log to verify in dev tools. Pitfall: forget document.body.appendChild and nothing displays.
Adding Babel Support
Why Babel? To transpile ES6+ (async/await, optional chaining) to ES5 for older browser compatibility. Create .babelrc or integrate into webpack.config.js.
Babel Loader and Preset
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
mode: 'development',
devServer: {
static: './dist',
hot: true,
port: 3000,
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin(),
],
module: {
rules: [
{
test: /\.scss$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
};Added Babel rule for .js files (excludes node_modules for performance). @babel/preset-env targets browsers via browserslist. Enable HMR with hot: true. Run npx webpack serve: JS/CSS changes reload instantly. Pitfall: without exclude: /node_modules/, builds slow dramatically.
SCSS File and HTML Template
$primary: #3498db;
.hello {
color: $primary;
font-family: Arial, sans-serif;
padding: 20px;
border: 2px solid $primary;
border-radius: 8px;
}This SCSS uses variables and nesting, compiled to CSS by chained loaders (sass → css → extract). Pair with a basic public/index.html with . The plugin automatically injects .
Optimized Production Configuration
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
...require('./webpack.config.js'),
mode: 'production',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
};Inherit from dev config via ...require('./webpack.config.js'). 'production' mode activates Terser (JS minification) and tree-shaking. [contenthash] enables cache busting; splitChunks separates vendor code (Lodash). Add package.json scripts: "build": "webpack --config webpack.prod.js". Run npm run build for optimized assets.
npm Scripts and HTML File
{
"name": "webpack-tutorial",
"version": "1.0.0",
"scripts": {
"dev": "webpack serve",
"build": "webpack --config webpack.prod.js",
"build:analyze": "webpack --config webpack.prod.js --profile --json > stats.json"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^7.1.0",
"html-webpack-plugin": "^5.6.0",
"mini-css-extract-plugin": "^2.9.0",
"sass": "^1.77.0",
"sass-loader": "^16.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"
}
}Scripts for dev (HMR), prod build, and analysis (webpack-bundle-analyzer via stats.json). 2026-compatible versions. Create public/index.html: for the template.
Best Practices
- Use
mode: 'production'for automatic minification and optimizations (avoids verbose configs). - Aggressive caching:
cache: { type: 'filesystem' }in devServer for 10x faster rebuilds. - Code splitting: Dynamic
import()for lazy-loading (e.g., routes). - Browserslist: Configure
.browserslistrcfor precise Babel targets (e.g., '> 0.5%, last 2 versions'). - Analyze bundles: Use
webpack-bundle-analyzerto spot bloated vendors.
Common Errors to Avoid
- HMR not working: Check
hot: trueanddevServer.static: './dist'; restart if port is busy. - CSS not applied: Reverse loader chain (postcss → css → extract); test without MiniCss in dev using style-loader.
- Slow builds: Exclude
node_modulesfrom Babel; usethread-loaderfor parallelization. - Polluted cache: Add
clean: trueor CleanWebpackPlugin; manually delete.cache/.
Further Reading
- Official documentation: Webpack 5 Guide
- Advanced: Integrate TypeScript (
ts-loader), images (file-loader), PWA (WorkboxWebpackPlugin) - Alternatives: Try esbuild or Rspack for speed
- Learni Dev Training: Webpack + Vite Masterclass for Seniors