| | |

esbuild 10 ⚡ Minification — –minify and the three minify flags

⏱️ Estimated read: 8 min | |

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

FlagEffect
--minifyAll three
--minify-whitespaceRemove spaces, newlines, comments
--minify-syntaxRewrite expressions to shorter forms
--minify-identifiersRename local variables

Related options

OptionPurpose
keepNamesPreserve function/class names
drop: ['console']Remove console statements
drop: ['debugger']Remove debugger statements
defineReplace identifiers at build time

Speed comparison

MinifierSpeedOutput size
esbuild20–40× faster~1–2% larger
TerserBaselineSlightly smaller

Vite configuration

SettingValue
build.minify'esbuild' (default)
build.minify'terser' (opt-in)
esbuild.keepNamestrue 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

PitfallWhy It HappensFix
Function names are minifiedminifyIdentifiers renames themAdd keepNames: true
Library breaks after minificationIt scans for specific function namesAdd keepNames: true
Console statements remainminify does not drop themAdd drop: ['console']
process.env.NODE_ENV is not replaceddefine not setAdd the define option
Build is slowTerser used instead of esbuildSwitch 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

ItemValue
Combined flag--minify
Whitespace flag--minify-whitespace
Syntax flag--minify-syntax
Identifiers flag--minify-identifiers
Name preservationkeepNames: true
Statement removaldrop: ['console', 'debugger']
Identifier replacementdefine
Speed vs Terser20–40× faster
Size vs Terser~1–2% larger (gzip)
Vite defaultbuild.minify: 'esbuild'

Key takeaways:

  • --minify enables 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.name sees the minified name. The keepNames option 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 .
  • drop and define are separate from minification. drop removes specific statements like console and debugger. define replaces identifiers at build time, which enables dead-code elimination for process.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.

Was this helpful?


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!