Webpack Basics

Module Bundling for Modern JavaScript

Welcome to the World of Module Bundling

Imagine you're preparing for a trip. You have clothes, toiletries, electronics, and documents scattered across your room. Webpack is like a smart suitcase that not only packs everything efficiently but also organizes items based on when you'll need them, compresses them to save space, and ensures nothing important is left behind. Today, we'll learn how to pack our JavaScript applications professionally!

Why Do We Need Module Bundlers?

In the early days of web development, we included JavaScript files like this:

<!-- The old way -->
<script src="jquery.js"></script>
<script src="lodash.js"></script>
<script src="utils.js"></script>
<script src="api.js"></script>
<script src="app.js"></script>

Problems with the Traditional Approach

graph TD A[Multiple Script Tags] --> B[Global Namespace Pollution] A --> C[Loading Order Issues] A --> D[Many HTTP Requests] A --> E[No Module System] F[Webpack Solution] --> G[Single Bundle File] F --> H[Module System] F --> I[Dependency Graph] F --> J[Code Optimization] style A fill:#f96,stroke:#333 style F fill:#6f9,stroke:#333

What is Webpack?

Webpack is a static module bundler that creates a dependency graph of your entire application and bundles it into one or more optimized files.

graph LR A[Entry Point] --> B[Module A] A --> C[Module B] B --> D[Module C] B --> E[Module D] C --> F[Module E] subgraph Input A B C D E F end G[Webpack] --> H[Bundle.js] subgraph Output H end F --> G E --> G D --> G style G fill:#f9f,stroke:#333,stroke-width:4px

Core Concepts

Setting Up Webpack

Installation

# Create a new project
mkdir webpack-demo
cd webpack-demo
npm init -y

# Install webpack and webpack-cli
npm install --save-dev webpack webpack-cli

# Create source directory
mkdir src
touch src/index.js

Basic Project Structure

webpack-demo/
├── node_modules/
├── src/
│   ├── index.js
│   ├── components/
│   │   ├── header.js
│   │   └── footer.js
│   └── utils/
│       └── helpers.js
├── dist/
├── package.json
├── package-lock.json
└── webpack.config.js

First Bundle

// src/index.js
import { formatDate } from './utils/helpers.js';
import { createHeader } from './components/header.js';

const header = createHeader('Welcome to Webpack!');
document.body.appendChild(header);

const today = formatDate(new Date());
console.log(`Today is ${today}`);

// src/utils/helpers.js
export function formatDate(date) {
    return date.toLocaleDateString();
}

// src/components/header.js
export function createHeader(text) {
    const header = document.createElement('h1');
    header.textContent = text;
    return header;
}

Running Webpack

# Add script to package.json
{
  "scripts": {
    "build": "webpack"
  }
}

# Run webpack
npm run build

# Webpack will create dist/main.js by default

Webpack Configuration

Webpack uses a configuration file to customize its behavior. This is like giving detailed instructions to our smart packing system.

Basic webpack.config.js

const path = require('path');

module.exports = {
  // Entry point of our application
  entry: './src/index.js',
  
  // Output configuration
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true // Clean the output directory before emit
  },
  
  // Development mode for better debugging
  mode: 'development'
};

Multiple Entry Points

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  },
  output: {
    filename: '[name].bundle.js', // app.bundle.js, admin.bundle.js
    path: path.resolve(__dirname, 'dist')
  }
};

Development vs Production

// webpack.config.js
const path = require('path');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    entry: './src/index.js',
    output: {
      filename: isProduction ? '[name].[contenthash].js' : '[name].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true
    },
    mode: isProduction ? 'production' : 'development',
    devtool: isProduction ? 'source-map' : 'eval-source-map'
  };
};

// package.json scripts
{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  }
}

Loaders: Teaching Webpack New Languages

Loaders are like translators that help webpack understand different file types. By default, webpack only understands JavaScript and JSON.

graph LR A[CSS File] --> B[css-loader] B --> C[style-loader] C --> D[JavaScript Module] E[SCSS File] --> F[sass-loader] F --> B G[Image File] --> H[file-loader] H --> I[URL/Path] J[TypeScript] --> K[ts-loader] K --> L[JavaScript] style B fill:#f9f,stroke:#333 style C fill:#f9f,stroke:#333 style F fill:#f9f,stroke:#333 style H fill:#f9f,stroke:#333 style K fill:#f9f,stroke:#333

CSS Loader Setup

# Install loaders
npm install --save-dev css-loader style-loader

# webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,  // Regular expression to match .css files
        use: ['style-loader', 'css-loader']  // Process right to left
      }
    ]
  }
};

// src/styles.css
.header {
  color: blue;
  font-size: 24px;
}

// src/index.js
import './styles.css';

const header = document.createElement('h1');
header.className = 'header';
header.textContent = 'Styled with Webpack!';
document.body.appendChild(header);

Asset Loading

# Install file-loader (or use built-in asset modules in webpack 5)
npm install --save-dev file-loader

# webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'  // Webpack 5 built-in
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource'
      }
    ]
  }
};

// src/index.js
import Logo from './logo.png';

const img = new Image();
img.src = Logo;
document.body.appendChild(img);

Popular Loaders

Babel Loader for Modern JavaScript

# Install babel and babel-loader
npm install --save-dev babel-loader @babel/core @babel/preset-env

# webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};

// .babelrc (alternative configuration)
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-transform-runtime"]
}

Sass/SCSS Loader

# Install sass and sass-loader
npm install --save-dev sass sass-loader css-loader style-loader

# webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',  // Creates style nodes from JS strings
          'css-loader',    // Translates CSS into CommonJS
          'sass-loader'    // Compiles Sass to CSS
        ]
      }
    ]
  }
};

// src/styles.scss
$primary-color: #007bff;

.button {
  background-color: $primary-color;
  &:hover {
    background-color: darken($primary-color, 10%);
  }
}

Plugins: Extending Webpack's Powers

While loaders work on individual files, plugins can affect the entire build process. They're like special tools in our packing system.

HtmlWebpackPlugin

# Install the plugin
npm install --save-dev html-webpack-plugin

# webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

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

// src/index.html (template)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

MiniCssExtractPlugin

# Install the plugin
npm install --save-dev mini-css-extract-plugin

# webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

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

Common Plugins

const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    // Clean dist folder before each build
    new CleanWebpackPlugin(),
    
    // Copy static files
    new CopyWebpackPlugin({
      patterns: [
        { from: 'public', to: 'static' }
      ]
    }),
    
    // Define environment variables
    new webpack.DefinePlugin({
      'process.env.API_URL': JSON.stringify('https://api.example.com')
    }),
    
    // Analyze bundle size
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false
    })
  ]
};

Development Server

Webpack Dev Server provides live reloading for a better development experience.

# Install webpack-dev-server
npm install --save-dev webpack-dev-server

# webpack.config.js
module.exports = {
  devServer: {
    static: './dist',
    hot: true,              // Hot Module Replacement
    port: 3000,             // Dev server port
    open: true,             // Open browser automatically
    historyApiFallback: true // For SPAs
  }
};

# package.json scripts
{
  "scripts": {
    "start": "webpack serve --mode development",
    "build": "webpack --mode production"
  }
}

Code Splitting

Code splitting is like creating multiple suitcases for different parts of your trip, loading only what you need when you need it.

Dynamic Imports

// Static import (included in main bundle)
import { heavyFunction } from './heavy-module';

// Dynamic import (separate chunk)
button.addEventListener('click', async () => {
  const module = await import('./heavy-module');
  module.heavyFunction();
});

// webpack.config.js for code splitting
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

Multiple Entry Points

module.exports = {
  entry: {
    home: './src/home.js',
    about: './src/about.js',
    contact: './src/contact.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      name: 'common'
    }
  }
};

Environment Configuration

Environment Variables

# Install dotenv-webpack
npm install --save-dev dotenv-webpack

# webpack.config.js
const Dotenv = require('dotenv-webpack');

module.exports = {
  plugins: [
    new Dotenv({
      systemvars: true  // Load system environment variables
    })
  ]
};

// .env file
API_URL=https://api.example.com
API_KEY=your-secret-key

// src/api.js
const apiUrl = process.env.API_URL;
const apiKey = process.env.API_KEY;

Multiple Configurations

// webpack.common.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

// 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: {
    static: './dist'
  }
});

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

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map'
});

Performance Optimization

Bundle Analysis

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

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

// package.json
{
  "scripts": {
    "analyze": "webpack --mode production --analyze"
  }
}

Tree Shaking

// package.json - mark as side-effect free
{
  "name": "my-library",
  "sideEffects": false
}

// Or specify files with side effects
{
  "sideEffects": ["*.css", "*.scss"]
}

// webpack.config.js
module.exports = {
  mode: 'production', // Tree shaking is enabled in production
  optimization: {
    usedExports: true
  }
};

Caching

module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    moduleIds: 'deterministic',
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

Real-World Webpack Configuration

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    entry: './src/index.js',
    output: {
      filename: isProduction ? '[name].[contenthash].js' : '[name].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
      publicPath: '/'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        },
        {
          test: /\.css$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i,
          type: 'asset/resource'
        }
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
        minify: isProduction
      }),
      isProduction && new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css'
      })
    ].filter(Boolean),
    optimization: {
      minimizer: [
        new TerserPlugin(),
        new CssMinimizerPlugin()
      ],
      splitChunks: {
        chunks: 'all'
      }
    },
    devServer: {
      static: './dist',
      hot: true,
      port: 3000,
      historyApiFallback: true
    },
    devtool: isProduction ? 'source-map' : 'eval-source-map'
  };
};

Practice Exercises

Exercise 1: Basic Setup

// Create a webpack project that:
// 1. Has src/index.js as entry point
// 2. Outputs to dist/bundle.js
// 3. Supports CSS files
// 4. Includes source maps for development
// 5. Has npm scripts for dev and build

Exercise 2: Asset Pipeline

// Enhance your webpack config to:
// 1. Process SCSS files
// 2. Handle image imports
// 3. Extract CSS to separate files in production
// 4. Add HTML template support
// 5. Implement cache busting with contenthash

Common Issues and Solutions

graph TD A[Common Webpack Issues] --> B[Module not found] A --> C[Loader conflicts] A --> D[Build performance] A --> E[Large bundle size] B --> B1[Check file paths and extensions] C --> C1[Check loader order and test regex] D --> D1[Use cache and exclude node_modules] E --> E1[Implement code splitting] style A fill:#f96,stroke:#333

Key Takeaways

Additional Resources