Skip to content
⚠️ This article was written in 2018. Some content may be outdated.

The Difference Between Webpack Loaders and Plugins

module.rules (loaders) and plugins are the two most important configuration sections in Webpack, but many people can't clearly distinguish between them.

Core Difference

Loader: file transformer
  - Acts on individual files
  - Converts file type A into a JS module Webpack understands
  - Triggered when a file is imported/required

Plugin: build-process extension
  - Acts on the entire build pipeline
  - Listens to Webpack lifecycle events and does extra work at the right time
  - More powerful, but also more complex to configure

Loader: File Transformation

javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: "vue-loader", // .vue file → JS module
      },
      {
        test: /\.tsx?$/,
        use: "ts-loader", // TypeScript → JS
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader", // 3. Inject CSS into the DOM
          "css-loader", // 2. Handle @import and url()
          "sass-loader", // 1. SCSS → CSS (executed right to left)
        ],
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 8192, // < 8 KB → convert to base64
          },
        },
      },
    ],
  },
};

Loaders execute right to left, bottom to top:

javascript
use: ["style-loader", "css-loader", "sass-loader"];
// Execution order: sass-loader → css-loader → style-loader

Plugin: Build Enhancement

javascript
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const webpack = require("webpack");

module.exports = {
  plugins: [
    // Auto-generate HTML and inject script tags
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      filename: "index.html",
    }),

    // Extract CSS into a separate file (instead of injecting via <style>)
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
    }),

    // Visualise bundle size (enable only when needed)
    process.env.ANALYZE && new BundleAnalyzerPlugin(),

    // Define global constants
    new webpack.DefinePlugin({
      "process.env.API_URL": JSON.stringify(process.env.API_URL),
    }),
  ].filter(Boolean),
};

Writing a Simple Loader

javascript
// my-loader.js
module.exports = function (source) {
  // source: the raw file content (string)
  // return: the transformed content

  // Example: strip all console.log calls from the file
  return source.replace(/console\.log\(.*?\);?\n?/g, "");
};

Writing a Simple Plugin

javascript
class BuildTimePlugin {
  apply(compiler) {
    // Listen for the done (build complete) event
    compiler.hooks.done.tap("BuildTimePlugin", (stats) => {
      const time = stats.endTime - stats.startTime;
      console.log(`\nBuild time: ${time}ms\n`);
    });
  }
}

module.exports = BuildTimePlugin;

Summary

LoaderPlugin
ScopeIndividual fileEntire build pipeline
TriggerWhen a file is importedWebpack lifecycle hooks
Config locationmodule.rulesplugins array
Typical useFile type conversionMinification, code splitting, HTML generation, variable injection

MIT Licensed