Webpack — Module Bundler
Webpack is the most widely adopted module bundler for JavaScript applications. It takes modules with dependencies and generates static assets representing those modules. With its extensive loader and plugin ecosystem, Webpack can process virtually any file type and transform it into optimized bundles.
From Create React App to Next.js (before Turbopack), Webpack has powered the majority of production React applications. While newer tools like Vite challenge its dominance, Webpack remains the most mature, configurable bundler with unparalleled plugin support.
The entry property tells Webpack where to start building the dependency graph. The output property tells Webpack where to emit the compiled bundles.
| 1 | const path = require("path"); |
| 2 | |
| 3 | module.exports = { |
| 4 | // Single entry point |
| 5 | entry: "./src/index.js", |
| 6 | |
| 7 | // Multiple entry points (multi-page app) |
| 8 | entry: { |
| 9 | main: "./src/main.js", |
| 10 | admin: "./src/admin.js", |
| 11 | vendor: ["react", "react-dom"], |
| 12 | }, |
| 13 | |
| 14 | output: { |
| 15 | path: path.resolve(__dirname, "dist"), |
| 16 | filename: "[name].[contenthash].js", |
| 17 | chunkFilename: "[name].[contenthash].chunk.js", |
| 18 | assetModuleFilename: "assets/[hash][ext][query]", |
| 19 | clean: true, // Clean dist before each build |
| 20 | publicPath: "/", |
| 21 | }, |
| 22 | }; |
| Output Template | Description | Example |
|---|---|---|
| [name] | Entry chunk name | main.js |
| [contenthash] | Hash based on content | main-a1b2c3.js |
| [fullhash] | Build-wide hash | main-x9y8z7.js |
| [id] | Internal chunk ID | 0.js |
| [ext] | File extension | main.css |
best practice
Loaders transform files from one format to another. They are the core of Webpack's versatility, enabling it to process TypeScript, CSS, images, fonts, and more. Loaders are configured in the module.rules array.
| 1 | module.exports = { |
| 2 | module: { |
| 3 | rules: [ |
| 4 | // JavaScript / TypeScript with Babel |
| 5 | { |
| 6 | test: /\.(js|jsx|ts|tsx)$/, |
| 7 | exclude: /node_modules/, |
| 8 | use: { |
| 9 | loader: "babel-loader", |
| 10 | options: { |
| 11 | presets: [ |
| 12 | ["@babel/preset-env", { targets: "> 0.25%, not dead" }], |
| 13 | "@babel/preset-react", |
| 14 | "@babel/preset-typescript", |
| 15 | ], |
| 16 | }, |
| 17 | }, |
| 18 | }, |
| 19 | |
| 20 | // CSS with PostCSS |
| 21 | { |
| 22 | test: /\.css$/, |
| 23 | use: [ |
| 24 | "style-loader", // Injects CSS into DOM |
| 25 | "css-loader", // Resolves CSS imports |
| 26 | "postcss-loader", // Autoprefixer, etc. |
| 27 | ], |
| 28 | }, |
| 29 | |
| 30 | // SCSS / Sass |
| 31 | { |
| 32 | test: /\.scss$/, |
| 33 | use: ["style-loader", "css-loader", "sass-loader"], |
| 34 | }, |
| 35 | |
| 36 | // CSS Modules |
| 37 | { |
| 38 | test: /\.module\.css$/, |
| 39 | use: [ |
| 40 | "style-loader", |
| 41 | { |
| 42 | loader: "css-loader", |
| 43 | options: { |
| 44 | modules: { |
| 45 | localIdentName: "[name]__[local]--[hash:base64:5]", |
| 46 | }, |
| 47 | }, |
| 48 | }, |
| 49 | ], |
| 50 | }, |
| 51 | |
| 52 | // Images and fonts |
| 53 | { |
| 54 | test: /\.(png|jpg|gif|svg|woff2?|eot|ttf)$/, |
| 55 | type: "asset/resource", |
| 56 | }, |
| 57 | |
| 58 | // Inline small assets as base64 |
| 59 | { |
| 60 | test: /\.(svg|ico)$/, |
| 61 | type: "asset/inline", |
| 62 | }, |
| 63 | ], |
| 64 | }, |
| 65 | }; |
warning
| Loader | Purpose | Type |
|---|---|---|
| babel-loader | Transpile JS/TS with Babel | JS |
| ts-loader | TypeScript compiler (full type check) | JS |
| css-loader | Resolves CSS imports & url() | CSS |
| style-loader | Injects CSS via <style> tags | CSS |
| sass-loader | Compile SCSS/Sass to CSS | CSS |
| postcss-loader | PostCSS transforms (Autoprefixer) | CSS |
| file-loader | Emit file to output (legacy) | Assets |
| url-loader | Inline small files as base64 (legacy) | Assets |
| svg-inline-loader | Inline SVGs as strings | Assets |
While loaders transform individual files, plugins can perform broader operations like bundle optimization, asset management, and environment injection.
| 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); |
| 2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); |
| 3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); |
| 4 | const { DefinePlugin } = require("webpack"); |
| 5 | const CopyWebpackPlugin = require("copy-webpack-plugin"); |
| 6 | |
| 7 | module.exports = { |
| 8 | plugins: [ |
| 9 | // Generates HTML with auto-injected script/link tags |
| 10 | new HtmlWebpackPlugin({ |
| 11 | template: "./public/index.html", |
| 12 | filename: "index.html", |
| 13 | favicon: "./public/favicon.ico", |
| 14 | }), |
| 15 | |
| 16 | // Multiple HTML pages |
| 17 | new HtmlWebpackPlugin({ |
| 18 | template: "./public/admin.html", |
| 19 | filename: "admin.html", |
| 20 | chunks: ["admin"], |
| 21 | }), |
| 22 | |
| 23 | // Extracts CSS into separate files (production only) |
| 24 | new MiniCssExtractPlugin({ |
| 25 | filename: "[name].[contenthash].css", |
| 26 | chunkFilename: "[id].[contenthash].css", |
| 27 | }), |
| 28 | |
| 29 | // Inject environment variables |
| 30 | new DefinePlugin({ |
| 31 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), |
| 32 | "process.env.API_URL": JSON.stringify("https://api.example.com"), |
| 33 | }), |
| 34 | |
| 35 | // Copy static files |
| 36 | new CopyWebpackPlugin({ |
| 37 | patterns: [ |
| 38 | { from: "public", to: ".", globOptions: { ignore: ["**/index.html"] } }, |
| 39 | ], |
| 40 | }), |
| 41 | ], |
| 42 | }; |
| Plugin | Purpose |
|---|---|
| HtmlWebpackPlugin | Generates HTML with auto-injected bundles |
| MiniCssExtractPlugin | Extracts CSS into separate files (instead of inline) |
| DefinePlugin | Injects compile-time constants |
| HotModuleReplacementPlugin | Enables HMR in development |
| ForkTsCheckerWebpackPlugin | TypeScript type-checking in separate process |
| BundleAnalyzerPlugin | Visualize bundle size with treemap |
| CopyWebpackPlugin | Copy static files to output |
| CompressionPlugin | Pre-compress assets (gzip, brotli) |
Webpack supports three approaches to code splitting: entry points, splitChunks configuration, and dynamic imports. Tree shaking eliminates unused exports through static analysis.
| 1 | module.exports = { |
| 2 | optimization: { |
| 3 | // Tree shaking — remove unused exports |
| 4 | usedExports: true, |
| 5 | sideEffects: true, |
| 6 | |
| 7 | // Minimization |
| 8 | minimize: true, |
| 9 | minimizer: ["...", new CssMinimizerPlugin()], |
| 10 | |
| 11 | // Code splitting |
| 12 | splitChunks: { |
| 13 | chunks: "all", |
| 14 | minSize: 20000, |
| 15 | maxSize: 244000, |
| 16 | minChunks: 1, |
| 17 | maxAsyncRequests: 30, |
| 18 | maxInitialRequests: 30, |
| 19 | cacheGroups: { |
| 20 | defaultVendors: { |
| 21 | test: /[\\/]node_modules[\\/]/, |
| 22 | priority: -10, |
| 23 | reuseExistingChunk: true, |
| 24 | name: "vendor", |
| 25 | }, |
| 26 | react: { |
| 27 | test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/, |
| 28 | priority: 10, |
| 29 | name: "react-vendor", |
| 30 | }, |
| 31 | common: { |
| 32 | minChunks: 2, |
| 33 | priority: -20, |
| 34 | reuseExistingChunk: true, |
| 35 | }, |
| 36 | }, |
| 37 | }, |
| 38 | |
| 39 | // Keep runtime chunk separate for long-term caching |
| 40 | runtimeChunk: "single", |
| 41 | }, |
| 42 | }; |
pro tip
The resolve configuration controls how Webpack resolves module requests. Aliases provide a cleaner import syntax and reduce relative path complexity.
| 1 | module.exports = { |
| 2 | resolve: { |
| 3 | // File extensions to resolve (in order) |
| 4 | extensions: [".js", ".jsx", ".ts", ".tsx", ".json", ".css"], |
| 5 | |
| 6 | // Directory to look for modules |
| 7 | modules: [path.resolve(__dirname, "src"), "node_modules"], |
| 8 | |
| 9 | // Import aliases |
| 10 | alias: { |
| 11 | "@": path.resolve(__dirname, "src"), |
| 12 | "@components": path.resolve(__dirname, "src/components"), |
| 13 | "@pages": path.resolve(__dirname, "src/pages"), |
| 14 | "@utils": path.resolve(__dirname, "src/utils"), |
| 15 | "@hooks": path.resolve(__dirname, "src/hooks"), |
| 16 | "@styles": path.resolve(__dirname, "src/styles"), |
| 17 | "@assets": path.resolve(__dirname, "src/assets"), |
| 18 | }, |
| 19 | |
| 20 | // Symlinks (for monorepos) |
| 21 | symlinks: true, |
| 22 | |
| 23 | // Condition names for package.json exports |
| 24 | conditionNames: ["import", "module", "require"], |
| 25 | }, |
| 26 | }; |
| 1 | // Without alias (painful relative paths) |
| 2 | import Button from "../../../../components/Button"; |
| 3 | import { formatDate } from "../../../../utils/date"; |
| 4 | |
| 5 | // With @ alias (clean, project-root-relative) |
| 6 | import Button from "@/components/Button"; |
| 7 | import { formatDate } from "@/utils/date"; |
| 8 | |
| 9 | // Also configure in tsconfig.json for IDE support |
| 10 | { |
| 11 | "compilerOptions": { |
| 12 | "baseUrl": ".", |
| 13 | "paths": { |
| 14 | "@/*": ["src/*"], |
| 15 | "@components/*": ["src/components/*"], |
| 16 | "@utils/*": ["src/utils/*"], |
| 17 | } |
| 18 | } |
| 19 | } |
info
Webpack DevServer provides a development server with live reloading and HMR. It uses WebSocket to push updates to the browser without a full page refresh.
| 1 | module.exports = { |
| 2 | devServer: { |
| 3 | static: { |
| 4 | directory: path.join(__dirname, "public"), |
| 5 | }, |
| 6 | port: 3000, |
| 7 | hot: true, // Enable HMR |
| 8 | open: true, // Open browser on start |
| 9 | historyApiFallback: true, // SPA routing support |
| 10 | compress: true, // Enable gzip compression |
| 11 | client: { |
| 12 | overlay: true, // Show errors in browser |
| 13 | logging: "warn", |
| 14 | }, |
| 15 | proxy: [ |
| 16 | { |
| 17 | context: ["/api", "/auth"], |
| 18 | target: "http://localhost:8080", |
| 19 | changeOrigin: true, |
| 20 | pathRewrite: { "^/api": "" }, |
| 21 | }, |
| 22 | ], |
| 23 | headers: { |
| 24 | "X-Custom-Header": "dev", |
| 25 | }, |
| 26 | watchFiles: ["src/**/*.php"], // Watch additional files |
| 27 | }, |
| 28 | }; |
warning
Module Federation is Webpack 5's most powerful feature. It enables loading modules from separately built applications at runtime, making micro-frontends practical without complex orchestration.
| 1 | // Host application (shell) |
| 2 | const { ModuleFederationPlugin } = require("webpack").container; |
| 3 | |
| 4 | module.exports = { |
| 5 | plugins: [ |
| 6 | new ModuleFederationPlugin({ |
| 7 | name: "shell", |
| 8 | remotes: { |
| 9 | app1: "app1@http://localhost:3001/remoteEntry.js", |
| 10 | app2: "app2@http://localhost:3002/remoteEntry.js", |
| 11 | }, |
| 12 | shared: { |
| 13 | react: { singleton: true, requiredVersion: "^18.0.0" }, |
| 14 | "react-dom": { singleton: true }, |
| 15 | "react-router-dom": { singleton: true }, |
| 16 | }, |
| 17 | }), |
| 18 | ], |
| 19 | }; |
| 20 | |
| 21 | // Remote application (exposes components) |
| 22 | module.exports = { |
| 23 | plugins: [ |
| 24 | new ModuleFederationPlugin({ |
| 25 | name: "app1", |
| 26 | filename: "remoteEntry.js", |
| 27 | exposes: { |
| 28 | "./Dashboard": "./src/components/Dashboard", |
| 29 | "./Header": "./src/components/Header", |
| 30 | "./store": "./src/store", |
| 31 | }, |
| 32 | shared: { |
| 33 | react: { singleton: true, requiredVersion: "^18.0.0" }, |
| 34 | "react-dom": { singleton: true }, |
| 35 | }, |
| 36 | }), |
| 37 | ], |
| 38 | }; |
note
| Strategy | Configuration | Impact |
|---|---|---|
| Cache | cache: { type: "filesystem" } | 50-80% faster rebuilds |
| Thread Loader | thread-loader on heavy loaders | 20-40% faster builds |
| SWC Loader | swc-loader (Rust) instead of babel-loader | 10-20x faster transpilation |
| ESBuild Minifier | esbuild-minify-plugin | 5-10x faster minification |
| DLL Plugin | DllPlugin (legacy, pre-v5) | Superseded by cache |
| 1 | module.exports = { |
| 2 | // Filesystem caching (Webpack 5) |
| 3 | cache: { |
| 4 | type: "filesystem", |
| 5 | cacheDirectory: path.resolve(__dirname, ".temp/cache"), |
| 6 | buildDependencies: { |
| 7 | config: [__filename], |
| 8 | }, |
| 9 | }, |
| 10 | |
| 11 | // Snapshot timings for node_modules |
| 12 | snapshot: { |
| 13 | managedPaths: [/node_modules/], |
| 14 | immutablePaths: [], |
| 15 | }, |
| 16 | |
| 17 | module: { |
| 18 | rules: [ |
| 19 | // Thread-loader for Babel (heavy) |
| 20 | { |
| 21 | test: /\.(js|ts)x?$/, |
| 22 | use: [ |
| 23 | { loader: "thread-loader", options: { workers: 4 } }, |
| 24 | "babel-loader", |
| 25 | ], |
| 26 | }, |
| 27 | ], |
| 28 | }, |
| 29 | }; |