cheat sheet
webpack
Daily-driver reference for webpack — CLI commands, dev server, loaders, plugins, code splitting, common config recipes.
webpack — CLI & config
What it is
webpack is the classic JS module bundler — loaders, plugins, code splitting, tree shaking, module federation. New apps lean toward Vite or Rspack, but webpack is still the most-deployed bundler in production. This page covers the CLI and config patterns you reach for daily.
For full ecosystem context (versioning, security, migrations), see packages-npm/npm-webpack.
Install
npm install -D webpack webpack-cli
# dev server (separate)
npm install -D webpack-dev-server
Output: webpack and webpack serve available via npx.
Day-to-day commands
| Command | What it does |
|---|---|
webpack | Build with webpack.config.js |
webpack --mode production | Build in prod mode (minify, optimise) |
webpack --mode development | Build in dev mode (faster, source maps) |
webpack --watch | Rebuild on file change |
webpack --config webpack.prod.js | Use a specific config file |
webpack --config-name web | Pick one config from an array of configs |
webpack serve | Run the dev server with HMR |
webpack serve --open | Same, auto-open browser |
webpack --analyze | Open webpack-bundle-analyzer report (with plugin) |
webpack --profile --json > stats.json | Emit build stats for analysis |
Common scenarios
Minimal config
// webpack.config.js
const path = require("node:path");
module.exports = {
mode: "production",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.[contenthash].js",
clean: true,
},
};
npx webpack
Output:
asset bundle.f3a9c2d8.js 1.2 KiB [emitted] [immutable]
webpack 5.96.0 compiled successfully in 154 ms
Dev server with HMR
// webpack.config.js
module.exports = {
mode: "development",
entry: "./src/index.js",
devServer: {
static: "./dist",
port: 3000,
hot: true,
open: true,
historyApiFallback: true, // SPA routing fallback
proxy: [{ context: ["/api"], target: "http://localhost:4000" }],
},
};
npx webpack serve
Output:
<i> [webpack-dev-server] Project is running at http://localhost:3000/
<i> [webpack-dev-server] webpack compiled successfully
Loader chain — TS + CSS + assets
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: "swc-loader", // ~5–10× faster than babel-loader
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(png|jpg|svg|woff2?)$/,
type: "asset/resource", // Asset Modules (webpack 5)
},
],
},
resolve: { extensions: [".ts", ".tsx", ".js"] },
};
Loaders apply right-to-left: css-loader parses imports, postcss-loader transforms, style-loader injects.
Plugins
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
plugins: [
new HtmlWebpackPlugin({ template: "./src/index.html" }),
new MiniCssExtractPlugin({ filename: "assets/[name].[contenthash].css" }),
],
};
Replace style-loader with MiniCssExtractPlugin.loader to emit .css files in production.
Code splitting
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: "react",
chunks: "all",
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
priority: -10,
},
},
},
runtimeChunk: "single",
}
Dynamic imports get their own chunks:
const Settings = () => import(/* webpackChunkName: "settings" */ "./Settings");
Multi-target build (browser + Node)
module.exports = [
{
name: "web",
target: "web",
entry: "./src/web/index.ts",
output: { path: __dirname + "/dist/web", filename: "bundle.js" },
},
{
name: "node",
target: "node",
entry: "./src/server/index.ts",
output: { path: __dirname + "/dist/server", filename: "bundle.cjs" },
externals: ["express"],
},
];
npx webpack # builds both
npx webpack --config-name web # builds only "web"
Output:
asset web.js 124 KiB [emitted]
asset node.js 38 KiB [emitted]
webpack 5.94.0 compiled successfully in 1842 ms
Tree shaking
// package.json
{
"sideEffects": ["*.css", "./src/polyfills.js"]
}
sideEffects: false for the whole package lets webpack drop unused exports. An array whitelists files that must remain.
Module Federation
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "host",
remotes: {
checkout: "checkout@https://checkout.example.com/remoteEntry.js",
},
shared: { react: { singleton: true }, "react-dom": { singleton: true } },
}),
],
};
Useful flags
| Flag | What it does |
|---|---|
--mode <dev/prod/none> | Set build mode |
--watch, -w | Rebuild on file change |
--config <path> | Path to config file |
--config-name <name> | Pick named config from array |
--env <key=val> | Pass env to config function (module.exports = (env) => {}) |
--devtool <variant> | Override devtool (e.g. source-map, eval-cheap-module-source-map) |
--profile --json > stats.json | Emit build stats |
--progress | Show progress bar |
--stats <preset> | Output verbosity (minimal, normal, verbose, errors-only) |
--target <env> | Override target (web, node, electron-main) |
webpack serve flags:
| Flag | What it does |
|---|---|
--open | Open browser on start |
--port <n> | Override port |
--host <ip> | Bind host |
--hot | Enable HMR (default) |
--no-hot | Disable HMR |
--https | Self-signed HTTPS |
Configuration
webpack's config can be a function:
module.exports = (env, argv) => ({
mode: argv.mode || "development",
entry: env.entry || "./src/index.js",
});
npx webpack --env entry=./src/main.js --mode production
Output:
asset main.js 78 KiB [emitted] [minimized]
webpack 5.94.0 compiled successfully in 2104 ms
webpack.config.ts also works with ts-node or tsx registered as a loader. Most projects stick to plain webpack.config.js.
Mode defaults
| Mode | Default optimization | Default devtool |
|---|---|---|
development | None | eval |
production | Minify, tree-shake, split-chunks | false |
none | Bare metal | false |
Common pitfalls
Module not found: Can't resolve 'X'. Missing install, missingresolve.extensions, or Node-only import in browser code (webpack 5 dropped polyfills — addresolve.fallbackor remove import).You may need an appropriate loader. A loader rule'stestregex doesn't match. Add.tsx?for both TS and TSX.- Slow builds. Swap
babel-loaderforswc-loader. Enablecache: { type: "filesystem" }. Narrow loaderincludepaths. - HMR doesn't fire. Module must call
module.hot.accept(); React/Vue plugins do this. Pure side-effect modules fall back to full reload. - Loaders applied in wrong order. Loaders run right-to-left in
use: [...]. Reverse mentally if confused. - Tree shaking didn't shrink bundle. Transitive CJS dep — CJS can't be tree-shaken. Swap for ESM alternative (
lodash→lodash-es). --inspectdebugging webpack-dev-server. Pass viaNODE_OPTIONS='--inspect-brk' npx webpack serve— DevServer wraps the build, not the user code, so debugger lands in webpack internals.
See also
- Packages: npm-webpack — versioning, security, alternatives
- JavaScript: modules — CJS/ESM interop
- JavaScript: vite — modern alternative for new projects