esbuild 10 ⚡ Minification — –minify and the three minify flags
esbuild’s --minify flag is a shorthand for enabling three separate transformations at once: minifyWhitespace, minifySyntax, and minifyIdentifiers. Each transformation targets a different kind of redundancy in the output. Whitespace minification removes spaces, newlines, and comments. Syntax minification rewrites expressions into shorter equivalent forms. Identifier minification renames local variables and function parameters to single-character names. The combined flag is what a production build uses by default, but the individual flags exist because sometimes only one of the three is appropriate.
This chapter covers the three minification passes, the --minify shorthand that enables all of them, the keepNames option that preserves function and class names for code that depends on them, and the benchmark data that explains why esbuild’s minifier is fast enough to be the default in Vite.
Key point: --minify is not a single operation. It is --minify-whitespace --minify-syntax --minify-identifiers combined. Disabling one of the three requires the individual flags, not the combined shorthand.
Why minification has three passes
The whitespace problem. JavaScript source contains spaces, tabs, newlines, and comments that have no effect on execution. Removing them reduces file size without changing behavior. The whitespace minifier also handles the subtle cases: it preserves the space between return and an expression, and it removes newlines only where automatic semicolon insertion rules allow it .
The syntax problem. Some expressions have shorter equivalent forms. true becomes !0. false becomes !1. undefined becomes void 0. A conditional expression that returns a boolean can sometimes be simplified. A block of statements can sometimes be merged. The syntax minifier applies these rewrites, which reduce size without renaming anything .
The identifier problem. Local variable names, function parameters, and function names are chosen by the developer for readability. The minifier renames them to short names (a, b, c, and so on), which is safe because the scope is known and the original names are not observable from outside the function. This is the transformation that produces the largest size reduction, but it is also the one that breaks code that depends on function names via Function.prototype.name or similar reflection .
a. The three individual flags
The minifyWhitespace, minifySyntax, and minifyIdentifiers options can be set independently in the JavaScript API or as separate CLI flags.
// All three
esbuild.build({ minify: true })
// Only whitespace
esbuild.build({ minifyWhitespace: true })
// Only syntax
esbuild.build({ minifySyntax: true })
// Only identifiers
esbuild.build({ minifyIdentifiers: true })
The CLI equivalents are --minify-whitespace, --minify-syntax, and --minify-identifiers. The combined --minify flag sets all three .
The distinction matters when a tool needs the output to remain readable. A build that minifies whitespace and syntax but not identifiers produces output that is compact but still has meaningful variable names, which is useful for debugging or for a library that ships unminified but compact code.
b. keepNames and the identifier caveat
Identifier minification renames functions and classes. Code that reads someFunction.name will see the minified name, not the original. This breaks frameworks that rely on function names for dependency injection, component registration, or debugging output.
The keepNames option preserves the name property by injecting a helper that assigns the original name. The trade-off is a small increase in output size for the functions whose names are preserved .
esbuild.build({
minify: true,
keepNames: true,
})
The Vite documentation notes that some libraries scan source code for specific function names (like twMerge or cn) and fail when the minifier renames them. keepNames: true is the fix for those cases .
c. The speed and size trade-off
esbuild’s minifier is not the smallest. Terser produces slightly smaller output in most benchmarks. The difference is typically 1–2% after gzip . The reason to use esbuild’s minifier is speed: benchmarks show it running 20–40× faster than Terser on large codebases .
The Vite documentation states this explicitly: esbuild minification is “20-40× faster than Terser with only 1-2% larger output” . For development builds, the speed is the dominant factor. For a production build where every kilobyte matters, Terser is the alternative, at the cost of build time.
The drop options (--drop:console, --drop:debugger) are separate from minification. They remove specific statements, not general redundancy. The define option replaces identifiers at build time, which is how process.env.NODE_ENV is replaced with "production" when all minification options are enabled .
Complete Example Session
// ============================================
// PART 1: THE COMBINED FLAG
// ============================================
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
minify: true,
outfile: 'dist/index.min.js',
})
// minify: true is equivalent to:
// minifyWhitespace: true
// minifySyntax: true
// minifyIdentifiers: true
// ============================================
// PART 2: WHITESPACE ONLY
// ============================================
esbuild.build({
minifyWhitespace: true,
})
// Removes spaces, newlines, comments.
// Variable names remain.
// ============================================
// PART 3: SYNTAX ONLY
// ============================================
esbuild.build({
minifySyntax: true,
})
// true → !0
// false → !1
// undefined → void 0
// ============================================
// PART 4: IDENTIFIERS ONLY
// ============================================
esbuild.build({
minifyIdentifiers: true,
})
// function calculateTotal() → function a()
// Local scope only.
// ============================================
// PART 5: KEEPNAMES
// ============================================
esbuild.build({
minify: true,
keepNames: true,
})
// Function names are preserved via a helper.
// Slightly larger output.
// ============================================
// PART 6: THE CLI FLAGS
// ============================================
// esbuild src/index.js --bundle --minify --outfile=dist/bundle.js
// esbuild src/index.js --bundle --minify-whitespace --outfile=dist/compact.js
// ============================================
// PART 7: DROP STATEMENTS
// ============================================
esbuild.build({
minify: true,
drop: ['console', 'debugger'],
})
// console.* and debugger statements are removed.
// This is separate from minification.
// ============================================
// PART 8: DEFINE REPLACEMENT
// ============================================
esbuild.build({
minify: true,
define: {
'process.env.NODE_ENV': '"production"',
},
})
// NODE_ENV is replaced at build time.
// Enables dead-code elimination.
// ============================================
// PART 9: THE BENCHMARK
// ============================================
// esbuild: 20-40× faster than Terser.
// Output: 1-2% larger after gzip.
// ============================================
// PART 10: THE VITE DEFAULT
// ============================================
// Vite uses esbuild for minification by default.
// build.minify: 'esbuild'
// Switch to 'terser' only when the size difference matters.
The ten parts covered the combined flag, whitespace-only, syntax-only, identifiers-only, keepNames, the CLI flags, drop, define, the benchmark, and the Vite default.
Quick Reference
Minification flags
| Flag | Effect |
|---|---|
--minify | All three |
--minify-whitespace | Remove spaces, newlines, comments |
--minify-syntax | Rewrite expressions to shorter forms |
--minify-identifiers | Rename local variables |
Related options
| Option | Purpose |
|---|---|
keepNames | Preserve function/class names |
drop: ['console'] | Remove console statements |
drop: ['debugger'] | Remove debugger statements |
define | Replace identifiers at build time |
Speed comparison
| Minifier | Speed | Output size |
|---|---|---|
| esbuild | 20–40× faster | ~1–2% larger |
| Terser | Baseline | Slightly smaller |
Vite configuration
| Setting | Value |
|---|---|
build.minify | 'esbuild' (default) |
build.minify | 'terser' (opt-in) |
esbuild.keepNames | true for name-dependent code |
Best Practices
✅ Do This:
// Use --minify for production
minify: true // ✅
// Use keepNames when code depends on function names
keepNames: true // ✅
// Use drop for console in production
drop: ['console', 'debugger'] // ✅
// Use define for NODE_ENV
define: { 'process.env.NODE_ENV': '"production"' } // ✅
❌ Don’t Do This:
// Assume --minify preserves function names
minify: true // function.name is minified // ❌
// Use --minify in development
minify: true // slows the build // ❌
// Forget keepNames for reflection-dependent code
minify: true // breaks DI frameworks // ❌
// Use Terser without measuring the size difference
minify: 'terser' // 20-40× slower // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Function names are minified | minifyIdentifiers renames them | Add keepNames: true |
| Library breaks after minification | It scans for specific function names | Add keepNames: true |
| Console statements remain | minify does not drop them | Add drop: ['console'] |
process.env.NODE_ENV is not replaced | define not set | Add the define option |
| Build is slow | Terser used instead of esbuild | Switch to esbuild minification |
Real-World Examples
1. Full minification
esbuild.build({ minify: true })
2. Whitespace only
esbuild.build({ minifyWhitespace: true })
3. Syntax only
esbuild.build({ minifySyntax: true })
4. Identifiers only
esbuild.build({ minifyIdentifiers: true })
5. keepNames
esbuild.build({ minify: true, keepNames: true })
6. Drop console
esbuild.build({ drop: ['console'] })
7. Define NODE_ENV
esbuild.build({ define: { 'process.env.NODE_ENV': '"production"' } })
8. CLI full minify
esbuild src/index.js --bundle --minify --outfile=dist/bundle.js
9. CLI whitespace only
esbuild src/index.js --bundle --minify-whitespace --outfile=dist/compact.js
10. Vite default
build: { minify: 'esbuild' }
Visual
The three passes
┌─────────────────────────────────────────────────────────────┐
│ minifyWhitespace │
│ Remove spaces, newlines, comments │
│ │
│ minifySyntax │
│ true → !0, false → !1, undefined → void 0 │
│ │
│ minifyIdentifiers │
│ function calculateTotal() → function a() │
│ │
│ --minify = all three │
└─────────────────────────────────────────────────────────────┘
The identifier caveat
┌─────────────────────────────────────────────────────────────┐
│ WITHOUT keepNames │
│ function myFunction() {} │
│ → function a() {} │
│ myFunction.name === "a" │
│ │
│ WITH keepNames │
│ function myFunction() {} │
│ → function a() {} │
│ + helper assigns .name = "myFunction" │
│ Slightly larger output. │
└─────────────────────────────────────────────────────────────┘
Speed vs size
┌─────────────────────────────────────────────────────────────┐
│ esbuild Terser │
│ 20-40× faster Baseline │
│ ~1-2% larger (gzip) Slightly smaller │
│ │
│ Use esbuild by default. │
│ Use Terser only when every kilobyte matters. │
└─────────────────────────────────────────────────────────────┘
The Vite default
┌─────────────────────────────────────────────────────────────┐
│ Vite build.minify │
│ 'esbuild' (default) │
│ 'terser' (opt-in) │
│ false (disable) │
│ │
│ Vite uses esbuild for minification by default. │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Combined flag | --minify |
| Whitespace flag | --minify-whitespace |
| Syntax flag | --minify-syntax |
| Identifiers flag | --minify-identifiers |
| Name preservation | keepNames: true |
| Statement removal | drop: ['console', 'debugger'] |
| Identifier replacement | define |
| Speed vs Terser | 20–40× faster |
| Size vs Terser | ~1–2% larger (gzip) |
| Vite default | build.minify: 'esbuild' |
Key takeaways:
--minifyenables three passes, not one. Whitespace, syntax, and identifiers are separate transformations. The combined flag sets all three, and the individual flags exist for cases where only one is appropriate .- Identifier minification renames functions and classes. Code that reads
function.namesees the minified name. ThekeepNamesoption preserves the original names at the cost of a small size increase . - esbuild’s minifier is fast, not the smallest. Terser produces slightly smaller output, but esbuild runs 20–40× faster. For most projects, the speed difference outweighs the 1–2% size difference .
dropanddefineare separate from minification.dropremoves specific statements likeconsoleanddebugger.definereplaces identifiers at build time, which enables dead-code elimination forprocess.env.NODE_ENV.- The default in Vite is esbuild. Vite uses esbuild for minification unless
build.minify: 'terser'is set explicitly. The switch to Terser is an opt-in for projects where the size difference justifies the build time .
Remember: Minification is three operations in one flag. The whitespace pass removes formatting, the syntax pass rewrites expressions, and the identifier pass renames local variables. The combined --minify flag enables all three, and that is what a production build uses. The one case that needs attention is code that depends on function names: keepNames: true preserves them, and the size cost is small compared to the breakage it prevents.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!