|$ curl https://forge-ai.dev/api/markdown?path=docs/build-tools/webpack
$cat docs/webpack-—-module-bundler.md
updated Recently·40 min read·published

Webpack — Module Bundler

Build ToolsWebpackBeginner to Advanced
Introduction

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.

Entry & Output

The entry property tells Webpack where to start building the dependency graph. The output property tells Webpack where to emit the compiled bundles.

webpack.config.js
JavaScript
1const path = require("path");
2
3module.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 TemplateDescriptionExample
[name]Entry chunk namemain.js
[contenthash]Hash based on contentmain-a1b2c3.js
[fullhash]Build-wide hashmain-x9y8z7.js
[id]Internal chunk ID0.js
[ext]File extensionmain.css

best practice

Always use [contenthash] in production filenames. It enables long-term caching — browsers keep the cached file until its content actually changes, and a new hash triggers a fresh download.
Loaders

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.

webpack.config.js
JavaScript
1module.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 order matters — Webpack applies them from right to left (bottom to top in the array). For CSS, the typical order is: sass-loadercss-loaderstyle-loader. Each loader processes the output of the next one.
LoaderPurposeType
babel-loaderTranspile JS/TS with BabelJS
ts-loaderTypeScript compiler (full type check)JS
css-loaderResolves CSS imports & url()CSS
style-loaderInjects CSS via <style> tagsCSS
sass-loaderCompile SCSS/Sass to CSSCSS
postcss-loaderPostCSS transforms (Autoprefixer)CSS
file-loaderEmit file to output (legacy)Assets
url-loaderInline small files as base64 (legacy)Assets
svg-inline-loaderInline SVGs as stringsAssets
Plugins

While loaders transform individual files, plugins can perform broader operations like bundle optimization, asset management, and environment injection.

webpack.config.js
JavaScript
1const HtmlWebpackPlugin = require("html-webpack-plugin");
2const MiniCssExtractPlugin = require("mini-css-extract-plugin");
3const { CleanWebpackPlugin } = require("clean-webpack-plugin");
4const { DefinePlugin } = require("webpack");
5const CopyWebpackPlugin = require("copy-webpack-plugin");
6
7module.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};
PluginPurpose
HtmlWebpackPluginGenerates HTML with auto-injected bundles
MiniCssExtractPluginExtracts CSS into separate files (instead of inline)
DefinePluginInjects compile-time constants
HotModuleReplacementPluginEnables HMR in development
ForkTsCheckerWebpackPluginTypeScript type-checking in separate process
BundleAnalyzerPluginVisualize bundle size with treemap
CopyWebpackPluginCopy static files to output
CompressionPluginPre-compress assets (gzip, brotli)
Code Splitting & Tree Shaking

Webpack supports three approaches to code splitting: entry points, splitChunks configuration, and dynamic imports. Tree shaking eliminates unused exports through static analysis.

webpack.config.js
JavaScript
1module.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 runtimeChunk: "single"setting extracts Webpack's runtime (module manifest, chunk loading logic) into its own file. Without this, the runtime is embedded in every entry chunk, breaking long-term caching when any chunk changes.
Resolve Aliases & Extensions

The resolve configuration controls how Webpack resolves module requests. Aliases provide a cleaner import syntax and reduce relative path complexity.

webpack.config.js
JavaScript
1module.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};
imports.tsx
TypeScript
1// Without alias (painful relative paths)
2import Button from "../../../../components/Button";
3import { formatDate } from "../../../../utils/date";
4
5// With @ alias (clean, project-root-relative)
6import Button from "@/components/Button";
7import { 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

Always mirror your Webpack resolve.alias configuration in tsconfig.json paths. This ensures your IDE (VSCode, WebStorm) resolves the same aliases for type checking, auto-completion, and Go to Definition.
DevServer

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.

webpack.config.js
JavaScript
1module.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

historyApiFallback: true is essential for SPAs using client-side routing (React Router, Vue Router). Without it, navigating directly to a route like /dashboard returns a 404 because the server has no file at that path.
Module Federation

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.

webpack.config.js
JavaScript
1// Host application (shell)
2const { ModuleFederationPlugin } = require("webpack").container;
3
4module.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)
22module.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

Module Federation requires shared dependencies to be singleton in most cases — otherwise, the host and remote each load their own copy of React, causing duplicate contexts, broken hooks, and inflated bundle sizes.
Performance Optimization
StrategyConfigurationImpact
Cachecache: { type: "filesystem" }50-80% faster rebuilds
Thread Loaderthread-loader on heavy loaders20-40% faster builds
SWC Loaderswc-loader (Rust) instead of babel-loader10-20x faster transpilation
ESBuild Minifieresbuild-minify-plugin5-10x faster minification
DLL PluginDllPlugin (legacy, pre-v5)Superseded by cache
webpack.config.js
JavaScript
1module.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};
Best Practices
Use filesystem cache for 50-80% faster rebuilds
Split configurations into webpack.common.js, webpack.dev.js, webpack.prod.js using webpack-merge
Prefer swc-loader over babel-loader for 10-20x faster transpilation
Use contenthash in production filenames for optimal caching
Extract CSS with MiniCssExtractPlugin in production (avoid style-loader)
Configure splitChunks to separate vendor, shared, and application code
Use BundleAnalyzerPlugin to audit bundle size before every major release
Set experiments.outputModule for ES module output when supported
Avoid inline JavaScript in HTML — use HtmlWebpackPlugin with script-src CSP
Monitor build times with webpack-bar or progress-plugin
$Blueprint — Engineering Documentation·Section ID: BT-WEBPACK-01·Revision: 1.0