Skip to content
Learni
View all tutorials
Outils de Build

How to Configure Webpack 5 for a Modern Project in 2026

Lire en français

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

terminal
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 lodash

This 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

package.json
{
  "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

src/index.js
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

webpack.config.js
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: Webpack.

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

.babelrc
{
  "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

webpack.config.js
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

src/index.js
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

webpack.config.js
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_ENV to conditionalize loaders/plugins, minimizing dev bundles.
  • Tree-shaking: Use named imports (e.g., import { debounce } from 'lodash'), and set sideEffects: false in 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
Explore our Learni trainings on advanced build tools to master Webpack for enterprise.