Webpack Configuration Setup

Creating Your First webpack.config.js File

Setting Up Your Build Kitchen

Welcome to Webpack configuration! If Webpack is like a professional kitchen, then the webpack.config.js file is your recipe book. Today, we'll learn how to write this recipe book from scratch, understanding each ingredient and cooking instruction along the way.

graph TD A[webpack.config.js] --> B[Entry Points] A --> C[Output Settings] A --> D[Module Rules] A --> E[Plugins] A --> F[Development Settings] B --> B1[Where to start bundling] C --> C1[Where to save bundles] D --> D1[How to process files] E --> E1[Extra build features] F --> F1[Dev tools & settings] style A fill:#f9f,stroke:#333,stroke-width:4px style B fill:#bbf,stroke:#333 style C fill:#bbf,stroke:#333 style D fill:#bbf,stroke:#333 style E fill:#bbf,stroke:#333 style F fill:#bbf,stroke:#333

Project Setup

Before we create our configuration, let's set up a new project. Think of this as preparing your kitchen before cooking:

# Create a new project directory
mkdir webpack-setup-demo
cd webpack-setup-demo

# Initialize npm project
npm init -y

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

# Create project structure
mkdir src
mkdir dist
touch src/index.js
touch webpack.config.js

Your project structure should look like this:

webpack-setup-demo/
├── dist/
├── src/
│   └── index.js
├── package.json
└── webpack.config.js

Basic Configuration Structure

Let's start with the simplest possible configuration. This is like learning to boil water before cooking complex dishes:

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

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

Let's break down each part:

Understanding Entry Points

The entry point is like the main ingredient in your recipe. Webpack starts here and follows all imports to build your application bundle.

Single Entry Point

// Simple entry
module.exports = {
  entry: './src/index.js'
};

// Equivalent to:
module.exports = {
  entry: {
    main: './src/index.js'
  }
};

Multiple Entry Points

// Multiple entries for different pages
module.exports = {
  entry: {
    home: './src/home.js',
    about: './src/about.js',
    contact: './src/contact.js'
  }
};
graph LR A[Multiple Entry Points] --> B[home.js] A --> C[about.js] A --> D[contact.js] B --> E[home.bundle.js] C --> F[about.bundle.js] D --> G[contact.bundle.js] style A fill:#f9f,stroke:#333 style E fill:#9f9,stroke:#333 style F fill:#9f9,stroke:#333 style G fill:#9f9,stroke:#333

Output Configuration

The output configuration tells Webpack how to write the compiled files to disk. It's like deciding how to plate and serve your finished dish.

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  },
  output: {
    filename: '[name].bundle.js',  // Uses entry key name
    path: path.resolve(__dirname, 'dist'),
    clean: true,  // Clean the output directory before emit
    publicPath: '/assets/',  // Public URL of output directory
  }
};

Filename Templates

Template Description Example Output
[name] Entry point name app.bundle.js
[contenthash] Hash of file content app.8e0d62a82.js
[fullhash] Hash of build app.50b8a3e1c.js
[id] Module identifier 0.bundle.js

Mode Configuration

Webpack has three modes that enable different built-in optimizations. It's like having preset cooking modes on your oven:

// Development mode
module.exports = {
  mode: 'development',
  // Enables:
  // - Readable output
  // - Fast incremental compilation
  // - Helpful error messages
};

// Production mode
module.exports = {
  mode: 'production',
  // Enables:
  // - Minification
  // - Scope hoisting
  // - Tree shaking
  // - Side-effect flagging
};

// None mode
module.exports = {
  mode: 'none',
  // Disables all optimizations
  // Used for debugging webpack itself
};

Module Rules (Loaders)

Module rules tell Webpack how to process different types of files. Think of loaders as specialized kitchen appliances for different ingredients:

module.exports = {
  module: {
    rules: [
      // CSS files
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      // Images
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      // Fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      // CSV/TSV files
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      // XML files
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
};

Anatomy of a Rule

graph TD A[Rule Object] --> B[test: regex pattern] A --> C[use: loader(s)] A --> D[type: asset type] A --> E[exclude: ignore paths] A --> F[include: only these paths] C --> C1[Single loader: 'css-loader'] C --> C2[Multiple: ['style-loader', 'css-loader']] C --> C3[With options: {loader: 'babel-loader', options: {...}}] style A fill:#f9f,stroke:#333

Resolve Configuration

The resolve configuration helps Webpack find modules. It's like telling your sous chef where to find ingredients:

module.exports = {
  resolve: {
    // File extensions to try when importing without extension
    extensions: ['.js', '.jsx', '.json', '.css'],
    
    // Directory names to look for when resolving modules
    modules: ['node_modules', 'src'],
    
    // Aliases for import paths
    alias: {
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@styles': path.resolve(__dirname, 'src/styles'),
    },
  },
};

// Now you can import like this:
// import Button from '@components/Button';
// instead of:
// import Button from '../../../components/Button';

DevServer Configuration

The dev server provides a development environment with live reloading. It's like having a test kitchen where you can taste as you cook:

module.exports = {
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    compress: true,
    port: 9000,
    hot: true,
    open: true,
    historyApiFallback: true,
    
    // Proxy API requests
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: { '^/api': '' },
      },
    },
  },
};

DevServer Features

Environment Variables

Use environment variables to create different configurations for different environments:

// webpack.config.js
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'),
    },
    devtool: isProduction ? 'source-map' : 'eval-source-map',
    plugins: [
      new webpack.DefinePlugin({
        'process.env.API_URL': JSON.stringify(
          isProduction ? 'https://api.production.com' : 'http://localhost:3000'
        ),
      }),
    ],
  };
};

Complete Example Configuration

Let's put it all together with a real-world configuration:

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

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    entry: {
      main: './src/index.js',
    },
    
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isProduction 
        ? '[name].[contenthash].js' 
        : '[name].js',
      clean: true,
    },
    
    mode: isProduction ? 'production' : 'development',
    
    devtool: isProduction ? 'source-map' : 'eval-source-map',
    
    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',
      }),
      isProduction && new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
      }),
    ].filter(Boolean),
    
    resolve: {
      extensions: ['.js', '.jsx'],
      alias: {
        '@': path.resolve(__dirname, 'src'),
      },
    },
    
    devServer: {
      static: './dist',
      hot: true,
      open: true,
      port: 3000,
    },
    
    optimization: {
      splitChunks: {
        chunks: 'all',
      },
    },
  };
};

Configuration Best Practices

1. Split Configurations

// webpack.common.js
module.exports = {
  entry: './src/index.js',
  // Common configuration
};

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

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

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

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

2. Use Environment Variables

// .env file
API_URL=https://api.example.com
DEBUG=true

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

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.API_URL': JSON.stringify(process.env.API_URL),
      'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
    }),
  ],
};

3. Keep It Organized

Debugging Your Configuration

When things go wrong, here are some debugging techniques:

// Add to webpack.config.js for debugging
module.exports = {
  // ... other config
  
  stats: {
    // Display bailout reasons
    optimizationBailout: true,
    // Display module reasons
    reasons: true,
    // Display chunk relations
    chunkRelations: true,
  },
  
  // Use verbose output
  infrastructureLogging: {
    level: 'verbose',
  },
};

// Run webpack with debugging flags
webpack --config webpack.config.js --profile --json > stats.json

// Analyze the stats file
webpack-bundle-analyzer stats.json

Common Pitfalls

1. Incorrect Path Resolution

// WRONG: Relative path in output
output: {
  path: './dist'  // This will fail
}

// CORRECT: Absolute path
output: {
  path: path.resolve(__dirname, 'dist')
}

2. Missing File Extensions

// Add extensions to resolve
resolve: {
  extensions: ['.js', '.jsx', '.json']
}

3. Loader Order

// Loaders execute from right to left
use: ['style-loader', 'css-loader', 'sass-loader']
// sass-loader → css-loader → style-loader

Practical Exercise

Create a Webpack configuration for a React application with the following requirements:

  1. Support for JSX files
  2. CSS modules with Sass preprocessing
  3. Image optimization
  4. Development server with HMR
  5. Production optimizations

Try to implement this configuration yourself, then check the solution below:

Click to see solution
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    entry: './src/index.jsx',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isProduction ? '[name].[contenthash].js' : '[name].js',
      clean: true,
    },
    
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/preset-react'],
            },
          },
        },
        {
          test: /\.module\.s[ac]ss$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
            {
              loader: 'css-loader',
              options: {
                modules: true,
              },
            },
            'sass-loader',
          ],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i,
          type: 'asset',
          parser: {
            dataUrlCondition: {
              maxSize: 8 * 1024, // 8kb
            },
          },
        },
      ],
    },
    
    plugins: [
      new HtmlWebpackPlugin({
        template: './public/index.html',
      }),
      isProduction && new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
      }),
    ].filter(Boolean),
    
    resolve: {
      extensions: ['.js', '.jsx'],
    },
    
    devServer: {
      hot: true,
      port: 3000,
    },
    
    optimization: {
      splitChunks: {
        chunks: 'all',
      },
    },
  };
};

Summary

Today we learned:

Remember: A good Webpack configuration is like a well-organized kitchen - everything has its place, and the workflow is smooth and efficient!