Skip to content

Webpack: Configuration, Loaders and Optimization

DodaTech Updated 2026-06-22 7 min read

In this tutorial, you'll learn Webpack including loaders, plugins, Code Splitting, Tree Shaking, caching strategies, and performance optimization for production builds.

Why Webpack Matters

Webpack is the most widely adopted module bundler in the JavaScript ecosystem. It powers thousands of production applications. Understanding Webpack means you can configure builds from scratch, debug bundling issues, and optimize bundle size for performance-critical applications. Even with modern alternatives like Vite emerging, Webpack remains essential for legacy projects and complex enterprise setups.

By the end of this guide, you will configure Webpack from scratch, use loaders and plugins, split code effectively, and optimize builds.

What is Webpack?

Webpack is a static module bundler that takes modules with dependencies and generates static assets representing those modules. It treats everything as a module -- JavaScript, CSS, images, fonts, and HTML.

flowchart LR
  A[Entry: index.js] --> B[Webpack]
  B --> C[Loaders]
  C --> D[CSS Loader]
  C --> E[Babel Loader]
  C --> F[File Loader]
  B --> G[Plugins]
  G --> H[HtmlWebpackPlugin]
  G --> I[MinicssExtractPlugin]
  B --> J[Output: bundle.js]

Basic Setup

mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack webpack-cli

webpack.config.js

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  mode: 'development',
}

Package.json Scripts

{
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch"
  }
}

Expected Output

$ npm run build

> webpack-demo@1.0.0 build
> webpack

asset bundle.js 21 KiB [emitted] (name: main)
./src/index.js 187 bytes [built] [code generated]
webpack 5.90.0 compiled successfully in 132 ms

Loaders

Loaders transform files before they are added to the bundle. They are the reason Webpack can handle CSS, images, TypeScript, and more.

Babel Loader (JavaScript/JSX)

npm install --save-dev babel-loader @babel/core @babel/preset-env
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['"@babel"/preset-env'],
          },
        },
      },
    ],
  },
}

CSS Loader

npm install --save-dev style-loader css-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
}

The order matters: css-loader interprets @import and url() in CSS. style-loader injects CSS into the DOM.

Asset Modules (Images, Fonts)

Webpack 5 has built-in asset modules. No separate loader needed.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
      },
    ],
  },
}

Plugins

Plugins extend Webpack's capabilities. They can optimize bundles, inject environment variables, generate HTML files, and more.

HtmlWebpackPlugin

npm install --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: 'My App',
      template: './src/index.html',
    }),
  ],
}

MiniCssExtractPlugin

Extracts CSS into separate files instead of inlining in JavaScript.

npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
  ],
}

Code Splitting

Code Splitting breaks your bundle into smaller chunks that can be loaded on demand.

Entry Points

module.exports = {
  entry: {
    main: './src/index.js',
    admin: './src/admin.js',
  },
  output: {
    filename: '[name].[contenthash].js',
  },
}

Dynamic Imports

// src/index.js
async function loadAdminPanel() {
  const module = await import('./adminPanel.js')
  module.init()
}

document.getElementById('admin-btn').addEventListener('click', loadAdminPanel)

Webpack automatically creates a separate chunk for adminPanel.js.

SplitChunksPlugin

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
}

Tree Shaking

Tree Shaking removes unused code. It works with ES module syntax (import/export), not CommonJS.

// src/math.js
export function square(x) { return x * x }
export function cube(x) { return x * x * x }

// src/index.js
import { square } from './math'
console.log(square(3))  // cube is tree-shaken away

Expected Output

The production bundle will contain only the square function. The cube function is removed during the build.

Enable Tree Shaking in production:

module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true,
    sideEffects: false,
  },
}

Caching Strategies

Use content hashes in filenames to enable long-term caching.

module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
  },
}

When file content changes, the hash changes. The browser fetches only the updated file.

Runtime Chunk

module.exports = {
  optimization: {
    runtimeChunk: 'single',
  },
}

This extracts the Webpack runtime into its own chunk, so vendor hashes remain stable.

Production Optimization

const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: { compress: { drop_console: true } },
      }),
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  performance: {
    hints: 'warning',
    maxEntrypointSize: 512000,
    maxAssetSize: 512000,
  },
})

Multi-Environment Config

// webpack.common.js
module.exports = {
  entry: './src/index.js',
  module: { /* shared rules */ },
  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: 'inline-source-map',
  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',
  optimization: { minimize: true },
})

Bundle Analysis

npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin(),
  ],
}

Run the build and the analyzer opens a dashboard showing bundle composition.

Common Errors

Problem Cause Fix
Module not found: Error: Can't resolve 'module' Missing npm package Install the package with npm
You may need an appropriate loader File type not handled Add a loader rule for that file type
Content hash changed despite no code change Module order changed Use optimization.moduleIds: 'deterministic'
MiniCssExtractPlugin doesn't work with style-loader Both cannot be used together Use one or the other based on environment
Entrypoint size exceeds the recommended limit Bundle too large Enable Code Splitting and Tree Shaking

Practice Questions

1. What is the purpose of loaders in Webpack?

Loaders transform files before they are added to the bundle (e.g., transpile TypeScript, process CSS).

2. How does Code Splitting improve performance?

It breaks the bundle into smaller chunks loaded on demand, reducing initial load time.

3. What is Tree Shaking and what syntax does it require?

Tree Shaking removes unused exports. It requires ES module syntax (import/export).

4. Why use contenthash in output filenames?

It creates unique filenames when content changes, enabling long-term browser caching.

5. What does the HtmlWebpackPlugin do?

It generates an HTML file that automatically includes the bundled scripts and stylesheets.

Challenge

Create a Webpack configuration for a TypeScript + React project with CSS modules, image handling, Code Splitting, and environment-specific configs. The production build should minify, tree-shake, and generate source maps.

Real-World Task

Take an existing JavaScript project and set up Webpack from scratch. Configure loaders for all file types used, enable Code Splitting for route-based pages, set up content hashing for caching, and analyze the bundle size. Reduce the total bundle size by at least 20% through Tree Shaking and Code Splitting.

Should I use Webpack 5 or Webpack 4?

Webpack 5 is the current major version. It includes built-in asset modules, better Tree Shaking, and persistent caching. Always use Webpack 5 for new projects.

What is the difference between Webpack and Vite?

Webpack bundles everything during development. Vite serves native ESM during development and bundles with Rollup for production. Vite is faster for development but Webpack has a richer plugin ecosystem.

How do I debug Webpack configuration?

Use webpack --stats verbose or add the BundleAnalyzerPlugin to visualize bundle composition. Set devtool: 'source-map' for readable stack traces.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro