[{"data":1,"prerenderedAt":1226},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification":1217},{"id":4,"title":5,"body":6,"description":1195,"extension":1196,"meta":1197,"navigation":1210,"path":1211,"seo":1212,"stem":1215,"__hash__":1216},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002Findex.md","esbuild vs Terser for production minification",{"type":7,"value":8,"toc":1181},"minimark",[9,13,28,31,36,39,83,86,90,198,202,205,210,213,229,284,288,299,310,382,386,397,422,508,512,522,540,669,704,708,711,828,892,896,899,927,1027,1035,1039,1122,1128,1134,1137,1141,1172,1177],[10,11,5],"h1",{"id":12},"esbuild-vs-terser-for-production-minification",[14,15,16,17,22,23,27],"p",{},"This comparison extends the ",[18,19,21],"a",{"href":20},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002F","tree-shaking and dead code elimination"," guide and the wider ",[18,24,26],{"href":25},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript bundle optimization and code splitting"," workstream by settling a narrow but consequential choice: when you minify production JavaScript, do you use esbuild or Terser?",[14,29,30],{},"Minification is where your last few kilobytes are squeezed out before the bytes hit the wire, and it is a direct lever on the initial route JS budget (target under 150KB gzipped) and on main-thread parse time (keep individual tasks under the 50ms long-task threshold). The two dominant minifiers make opposite bets. esbuild is written in Go and minifies an order of magnitude faster than anything JavaScript-based, accepting a slightly larger output. Terser is the long-standing JavaScript minifier whose multi-pass analysis squeezes out the last percent of size at a much higher time cost. This page compares them on the four axes that matter — speed, compression ratio, mangling safety, and dead-code elimination — gives you a benchmark workflow to decide for your own bundle, and locks the result in with a CI assertion.",[32,33,35],"h2",{"id":34},"rapid-diagnosis-which-minifier-do-you-actually-need","Rapid diagnosis: which minifier do you actually need?",[14,37,38],{},"Before benchmarking, triage with this checklist. For most apps the answer is decided in under a minute:",[40,41,42,55,61,67,77],"ul",{},[43,44,45,49,50,54],"li",{},[46,47,48],"strong",{},"Is minify on your critical path?"," Run ",[51,52,53],"code",{},"time npx \u003Ctool> ..."," on your real bundle. If the minify step adds seconds to every watch rebuild or blocks CI, the speed axis dominates and esbuild wins outright.",[43,56,57,60],{},[46,58,59],{},"Is your gzipped route budget already comfortably under 150KB?"," If yes, the ~1-3% that Terser might recover is noise — take esbuild's speed.",[43,62,63,66],{},[46,64,65],{},"Are you shipping a byte-contested artifact?"," A third-party embeddable widget, an ad tag, or a service-worker payload where every kilobyte is billed or budgeted is the one case where Terser's extra passes can earn their time.",[43,68,69,72,73,76],{},[46,70,71],{},"Does anything at runtime reflect on identifier names?"," DI containers, decorators, ",[51,74,75],{},"Function.prototype.name",", or error-reporting that groups by function name need name preservation — check which tool exposes the controls you need before switching.",[43,78,79,82],{},[46,80,81],{},"Are you still seeing dead code in the output?"," That is almost always a bundler tree-shaking gap, not a minifier gap — fix it upstream first (see the root-cause section below).",[14,84,85],{},"If none of the last three fire, stop here and use esbuild. The sections below explain why, and how to prove it for your own code.",[32,87,89],{"id":88},"the-speed-versus-bytes-trade-in-one-view","The speed-versus-bytes trade in one view",[14,91,92],{},[93,94,101,102,101,106,101,110,101,120,101,127,101,134,101,139,101,145,101,150,101,154,101,157,101,161,101,164,101,167,101,171,101,174,101,177,101,181,101,184,101,187,101,190,101,194,101],"svg",{"xmlns":95,"viewBox":96,"width":97,"role":98,"ariaLabel":99,"style":100},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 320","100%","img","Decision matrix comparing esbuild and Terser on minify speed, compression ratio, mangling safety, and dead-code elimination","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[103,104,105],"title",{},"esbuild vs Terser matrix",[107,108,109],"desc",{},"A four-row matrix scoring esbuild and Terser on the criteria that affect minified output and build time.",[111,112],"rect",{"x":113,"y":113,"width":114,"height":115,"rx":116,"fill":117,"stroke":118,"style":119},"1","758","318","10","none","currentColor","stroke-opacity:0.18",[121,122,126],"text",{"x":123,"y":124,"fill":118,"style":125},"24","38","font-size:18px;font-weight:700","Minifier decision matrix",[121,128,133],{"x":129,"y":130,"fill":131,"style":132},"430","68","#0466c8","font-size:13px;font-weight:600;text-anchor:middle","esbuild",[121,135,138],{"x":136,"y":130,"fill":137,"style":132},"640","#b8860b","Terser",[140,141],"line",{"x1":123,"y1":142,"x2":143,"y2":142,"stroke":118,"style":144},"78","736","stroke-opacity:0.3",[121,146,149],{"x":123,"y":147,"fill":118,"style":148},"112","font-size:13px","Minify speed",[121,151,153],{"x":129,"y":147,"fill":118,"style":152},"font-size:13px;text-anchor:middle","~20-100x faster",[121,155,156],{"x":136,"y":147,"fill":118,"style":152},"baseline",[121,158,160],{"x":123,"y":159,"fill":118,"style":148},"150","Compression ratio",[121,162,163],{"x":129,"y":159,"fill":118,"style":152},"within ~1-3%",[121,165,166],{"x":136,"y":159,"fill":118,"style":152},"smallest",[121,168,170],{"x":123,"y":169,"fill":118,"style":148},"188","Mangling safety",[121,172,173],{"x":129,"y":169,"fill":118,"style":152},"safe defaults",[121,175,176],{"x":136,"y":169,"fill":118,"style":152},"tunable",[121,178,180],{"x":123,"y":179,"fill":118,"style":148},"226","Dead-code elim.",[121,182,183],{"x":129,"y":179,"fill":118,"style":152},"single-pass",[121,185,186],{"x":136,"y":179,"fill":118,"style":152},"multi-pass",[140,188],{"x1":123,"y1":189,"x2":143,"y2":189,"stroke":118,"style":144},"246",[121,191,193],{"x":123,"y":192,"fill":118,"style":148},"278","esbuild trades a few percent of bytes for a huge build-time win.",[121,195,197],{"x":123,"y":196,"fill":118,"style":148},"302","Terser earns its time cost only when every byte is contested.",[32,199,201],{"id":200},"root-cause-the-four-axes-that-separate-them","Root cause: the four axes that separate them",[14,203,204],{},"The two tools diverge on four measurable properties. Each is a failure mode if you optimize for the wrong one.",[206,207,209],"h3",{"id":208},"minify-speed-the-headline-difference","Minify speed: the headline difference",[14,211,212],{},"Speed is the reason esbuild exists. Because it is compiled Go with a parallel architecture, it routinely minifies a large bundle 20 to 100 times faster than Terser, which runs single-threaded in Node. On a multi-megabyte input this is the difference between a minify pass measured in tens of milliseconds versus several seconds. In a watch or preview build the gap is felt every iteration; in CI it shrinks a step that often sits on the critical path.",[14,214,215,216,219,220,223,224,228],{},"This speed is why bundlers increasingly default to esbuild for minification. Vite uses esbuild as its default minifier; webpack ships ",[51,217,218],{},"TerserPlugin"," by default but lets you swap in ",[51,221,222],{},"esbuild-loader","'s minifier. The speed difference does not change a single byte your users download — it changes how fast ",[225,226,227],"em",{},"you"," ship.",[230,231,236],"pre",{"className":232,"code":233,"language":234,"meta":235,"style":235},"language-bash shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# Compare minify wall-clock on the same bundle (median of 3 runs)\ntime npx esbuild app.js --minify --bundle --outfile=out.esbuild.js\ntime npx terser app.bundle.js -c -m -o out.terser.js\n# trade-off: build speed only matters if minify is on your critical path —\n# if CI caches the build, optimizing this number buys your users nothing.\n","bash","",[51,237,238,246,264,272,278],{"__ignoreMap":235},[239,240,242],"span",{"class":140,"line":241},1,[239,243,245],{"class":244},"sIIH1","# Compare minify wall-clock on the same bundle (median of 3 runs)\n",[239,247,249,253,257,260],{"class":140,"line":248},2,[239,250,252],{"class":251},"sP5qI","time",[239,254,256],{"class":255},"syybb"," npx esbuild app.js --minify --bundle --outfile",[239,258,259],{"class":251},"=",[239,261,263],{"class":262},"s-_DF","out.esbuild.js\n",[239,265,267,269],{"class":140,"line":266},3,[239,268,252],{"class":251},[239,270,271],{"class":255}," npx terser app.bundle.js -c -m -o out.terser.js\n",[239,273,275],{"class":140,"line":274},4,[239,276,277],{"class":244},"# trade-off: build speed only matters if minify is on your critical path —\n",[239,279,281],{"class":140,"line":280},5,[239,282,283],{"class":244},"# if CI caches the build, optimizing this number buys your users nothing.\n",[206,285,287],{"id":286},"compression-ratio-how-much-smaller-is-terser-really","Compression ratio: how much smaller is Terser, really",[14,289,290,291,294,295,298],{},"Terser does win on raw output size, but the margin is narrower than its reputation suggests. With multi-pass ",[51,292,293],{},"compress"," enabled, Terser typically emits output a few percent smaller than esbuild before gzip; after gzip the difference often shrinks to roughly 1-3% because the compressor recovers much of the redundancy esbuild leaves behind. On a 150KB gzipped initial route that is single-digit kilobytes — real, but small relative to what a single unnecessary dependency or a missed ",[18,296,297],{"href":20},"tree-shaking opportunity"," costs you.",[14,300,301,302,305,306,309],{},"The practical implication: chase the dependency graph before you chase the minifier. If your bundle is bloated, switching from esbuild to Terser recovers a few percent; removing a mis-shaken ",[51,303,304],{},"lodash"," or ",[51,307,308],{},"moment"," import recovers far more.",[230,311,315],{"className":312,"code":313,"language":314,"meta":235,"style":235},"language-js shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F terser config that maximizes ratio — and the time it costs\nmodule.exports = {\n  compress: { passes: 2 },   \u002F\u002F extra passes find more shrinking opportunities\n  mangle: { toplevel: true },\n  \u002F\u002F trade-off: passes: 2 noticeably increases minify time for ~1% extra savings;\n  \u002F\u002F skip it unless you are genuinely byte-constrained on a hot path.\n};\n","js",[51,316,317,322,340,354,365,370,376],{"__ignoreMap":235},[239,318,319],{"class":140,"line":241},[239,320,321],{"class":244},"\u002F\u002F terser config that maximizes ratio — and the time it costs\n",[239,323,324,328,331,334,337],{"class":140,"line":248},[239,325,327],{"class":326},"sf6mN","module",[239,329,330],{"class":255},".",[239,332,333],{"class":326},"exports",[239,335,336],{"class":251}," =",[239,338,339],{"class":255}," {\n",[239,341,342,345,348,351],{"class":140,"line":266},[239,343,344],{"class":255},"  compress: { passes: ",[239,346,347],{"class":326},"2",[239,349,350],{"class":255}," },   ",[239,352,353],{"class":244},"\u002F\u002F extra passes find more shrinking opportunities\n",[239,355,356,359,362],{"class":140,"line":274},[239,357,358],{"class":255},"  mangle: { toplevel: ",[239,360,361],{"class":326},"true",[239,363,364],{"class":255}," },\n",[239,366,367],{"class":140,"line":280},[239,368,369],{"class":244},"  \u002F\u002F trade-off: passes: 2 noticeably increases minify time for ~1% extra savings;\n",[239,371,373],{"class":140,"line":372},6,[239,374,375],{"class":244},"  \u002F\u002F skip it unless you are genuinely byte-constrained on a hot path.\n",[239,377,379],{"class":140,"line":378},7,[239,380,381],{"class":255},"};\n",[206,383,385],{"id":384},"mangling-safety-where-correctness-bugs-hide","Mangling safety: where correctness bugs hide",[14,387,388,389,392,393,396],{},"Mangling renames identifiers to single characters to save bytes, and this is where a minifier can silently break code. Both tools mangle local scope safely by default. The danger is ",[51,390,391],{},"mangle.properties"," (Terser) or ",[51,394,395],{},"--mangle-props"," (esbuild): renaming object properties breaks any code that accesses them by string key, reflects over them, or relies on a serialized shape (e.g. a property name sent to an API). Neither tool enables property mangling by default, and you should leave it off unless you have a reserved-name allowlist and thorough tests.",[14,398,399,400,403,404,403,407,410,411,413,414,417,418,421],{},"Terser exposes finer-grained safety knobs — ",[51,401,402],{},"keep_classnames",", ",[51,405,406],{},"keep_fnames",[51,408,409],{},"reserved"," lists, and per-option ",[51,412,293],{}," toggles — which matters when a framework or error-reporting tool depends on stable function names. esbuild offers ",[51,415,416],{},"--keep-names"," to preserve ",[51,419,420],{},"name"," properties but exposes fewer dials overall, trading configurability for a smaller correctness surface.",[230,423,425],{"className":312,"code":424,"language":314,"meta":235,"style":235},"\u002F\u002F Preserve names for code that reflects on them (DI, decorators, error stacks)\n\u002F\u002F esbuild:\n\u002F\u002F esbuild app.js --minify --keep-names\n\u002F\u002F terser:\nmodule.exports = {\n  keep_classnames: true,  \u002F\u002F some DI containers resolve by class name\n  keep_fnames: \u002FComponent$\u002F, \u002F\u002F preserve React component display names\n  \u002F\u002F trade-off: keeping names increases output size; only preserve the exact\n  \u002F\u002F patterns your runtime reflects on, not everything, or you give back the win.\n};\n",[51,426,427,432,437,442,447,459,472,491,497,503],{"__ignoreMap":235},[239,428,429],{"class":140,"line":241},[239,430,431],{"class":244},"\u002F\u002F Preserve names for code that reflects on them (DI, decorators, error stacks)\n",[239,433,434],{"class":140,"line":248},[239,435,436],{"class":244},"\u002F\u002F esbuild:\n",[239,438,439],{"class":140,"line":266},[239,440,441],{"class":244},"\u002F\u002F esbuild app.js --minify --keep-names\n",[239,443,444],{"class":140,"line":274},[239,445,446],{"class":244},"\u002F\u002F terser:\n",[239,448,449,451,453,455,457],{"class":140,"line":280},[239,450,327],{"class":326},[239,452,330],{"class":255},[239,454,333],{"class":326},[239,456,336],{"class":251},[239,458,339],{"class":255},[239,460,461,464,466,469],{"class":140,"line":372},[239,462,463],{"class":255},"  keep_classnames: ",[239,465,361],{"class":326},[239,467,468],{"class":255},",  ",[239,470,471],{"class":244},"\u002F\u002F some DI containers resolve by class name\n",[239,473,474,477,480,483,486,488],{"class":140,"line":378},[239,475,476],{"class":255},"  keep_fnames:",[239,478,479],{"class":262}," \u002FComponent",[239,481,482],{"class":251},"$",[239,484,485],{"class":262},"\u002F",[239,487,403],{"class":255},[239,489,490],{"class":244},"\u002F\u002F preserve React component display names\n",[239,492,494],{"class":140,"line":493},8,[239,495,496],{"class":244},"  \u002F\u002F trade-off: keeping names increases output size; only preserve the exact\n",[239,498,500],{"class":140,"line":499},9,[239,501,502],{"class":244},"  \u002F\u002F patterns your runtime reflects on, not everything, or you give back the win.\n",[239,504,506],{"class":140,"line":505},10,[239,507,381],{"class":255},[206,509,511],{"id":510},"dead-code-elimination-single-pass-speed-versus-multi-pass-thoroughness","Dead-code elimination: single-pass speed versus multi-pass thoroughness",[14,513,514,515,518,519,521],{},"Both minifiers perform dead-code elimination — dropping unreachable branches, unused locals, and ",[51,516,517],{},"if (false)"," blocks — but their depth differs. esbuild does competent single-pass DCE that catches the common cases. Terser's multi-pass ",[51,520,293],{}," can iterate, so eliminations that expose further eliminations get caught on a later pass, occasionally removing code esbuild leaves behind.",[14,523,524,525,528,529,532,533,535,536,330],{},"Crucially, neither minifier replaces bundler-level tree-shaking. Removing an unused ",[225,526,527],{},"export"," across module boundaries is the bundler's job (Rollup\u002Fwebpack), driven by the ",[51,530,531],{},"sideEffects"," field; the minifier only prunes within what the bundler already included. So the order of operations is: get tree-shaking right at the bundler, then let the minifier clean up the residue. If you are still seeing dead code in the output, the fix usually belongs upstream — see the dependency-graph techniques in ",[18,534,21],{"href":20}," and the bundler comparison in ",[18,537,539],{"href":538},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002F","Vite vs webpack bundle splitting performance",[93,541,544,545,544,548,544,551,544,569,544,572,544,575,544,582,544,586,544,591,544,594,544,600,544,605,544,609,544,613,544,616,544,619,544,623,544,626,544,629,544,633,544,636,544,639,544,646,544,650,544,654,544,657,544,661,544,665],{"xmlns":95,"viewBox":542,"width":97,"role":98,"ariaLabel":543,"style":100},"0 0 760 300","Byte-removal pipeline: source modules feed bundler tree-shaking, which drops unused exports across module boundaries, then the minifier does dead-code elimination and mangling on what remains, then gzip compresses the rest — showing the minifier as the last and smallest lever","\n  ",[103,546,547],{},"Where each stage removes bytes",[107,549,550],{},"A left-to-right pipeline. The bundler tree-shakes whole unused exports across module boundaries; the minifier then only prunes dead branches and renames identifiers inside the code the bundler kept; gzip compresses the remainder. The minifier is the last and smallest lever.",[552,553,554,555,544],"defs",{},"\n    ",[556,557,564,565,554],"marker",{"id":558,"viewBox":559,"refX":560,"refY":561,"markerWidth":562,"markerHeight":562,"orient":563},"pipe-arrow","0 0 10 10","9","5","7","auto-start-reverse","\n      ",[566,567],"path",{"d":568,"fill":118},"M0,0 L10,5 L0,10 z",[111,570],{"x":113,"y":113,"width":114,"height":571,"rx":116,"fill":117,"stroke":118,"style":119},"298",[121,573,547],{"x":123,"y":574,"fill":118,"style":125},"40",[111,576],{"x":123,"y":577,"width":578,"height":579,"rx":580,"fill":117,"stroke":118,"style":581},"76","154","88","6","stroke-opacity:0.4",[111,583],{"x":584,"y":577,"width":578,"height":579,"rx":580,"fill":131,"stroke":131,"style":585},"210","fill-opacity:0.14",[111,587],{"x":588,"y":577,"width":578,"height":579,"rx":580,"fill":589,"stroke":137,"style":590},"396","#ffc300","fill-opacity:0.22",[111,592],{"x":593,"y":577,"width":578,"height":579,"rx":580,"fill":117,"stroke":118,"style":581},"582",[121,595,599],{"x":596,"y":597,"fill":118,"style":598},"101","104","font-size:12px;font-weight:700;text-anchor:middle","Source modules",[121,601,604],{"x":596,"y":602,"fill":118,"style":603},"128","font-size:11px;text-anchor:middle","the full input —",[121,606,608],{"x":596,"y":607,"fill":118,"style":603},"146","authored + deps",[121,610,612],{"x":611,"y":597,"fill":118,"style":598},"287","Bundler tree-shaking",[121,614,615],{"x":611,"y":602,"fill":118,"style":603},"drops unused exports",[121,617,618],{"x":611,"y":607,"fill":118,"style":603},"across modules",[121,620,622],{"x":621,"y":597,"fill":118,"style":598},"473","Minifier DCE + mangle",[121,624,625],{"x":621,"y":602,"fill":118,"style":603},"prunes residue inside",[121,627,628],{"x":621,"y":607,"fill":118,"style":603},"the kept code",[121,630,632],{"x":631,"y":597,"fill":118,"style":598},"659","gzip",[121,634,635],{"x":631,"y":602,"fill":118,"style":603},"compresses the",[121,637,638],{"x":631,"y":607,"fill":118,"style":603},"bytes that remain",[140,640],{"x1":641,"y1":642,"x2":643,"y2":642,"stroke":118,"style":644,"markerEnd":645},"180","120","206","stroke-opacity:0.55","url(#pipe-arrow)",[140,647],{"x1":648,"y1":642,"x2":649,"y2":642,"stroke":118,"style":644,"markerEnd":645},"366","392",[140,651],{"x1":652,"y1":642,"x2":653,"y2":642,"stroke":118,"style":644,"markerEnd":645},"552","578",[140,655],{"x1":123,"y1":656,"x2":143,"y2":656,"stroke":118,"style":144},"196",[121,658,660],{"x":123,"y":659,"fill":118,"style":148},"224","Get tree-shaking right at the bundler first — it removes whole modules the minifier never sees.",[121,662,664],{"x":123,"y":663,"fill":118,"style":148},"252","The minifier (highlighted) only prunes and renames within what the bundler kept — the smallest lever.",[121,666,668],{"x":123,"y":667,"fill":118,"style":148},"280","gzip then compresses the remainder, where the ~1-3% esbuild-vs-Terser gap often shrinks further.",[230,670,672],{"className":312,"code":671,"language":314,"meta":235,"style":235},"\u002F\u002F Define-replace lets BOTH minifiers DCE dead branches at build time\n\u002F\u002F esbuild: --define:process.env.NODE_ENV='\"production\"'\n\u002F\u002F This turns `if (process.env.NODE_ENV !== 'production')` into `if (false)`,\n\u002F\u002F which the minifier then drops entirely.\n\u002F\u002F trade-off: getting the define wrong (or forgetting it) leaves dev-only\n\u002F\u002F warning code in the production bundle, inflating bytes and parse time.\n",[51,673,674,679,684,689,694,699],{"__ignoreMap":235},[239,675,676],{"class":140,"line":241},[239,677,678],{"class":244},"\u002F\u002F Define-replace lets BOTH minifiers DCE dead branches at build time\n",[239,680,681],{"class":140,"line":248},[239,682,683],{"class":244},"\u002F\u002F esbuild: --define:process.env.NODE_ENV='\"production\"'\n",[239,685,686],{"class":140,"line":266},[239,687,688],{"class":244},"\u002F\u002F This turns `if (process.env.NODE_ENV !== 'production')` into `if (false)`,\n",[239,690,691],{"class":140,"line":274},[239,692,693],{"class":244},"\u002F\u002F which the minifier then drops entirely.\n",[239,695,696],{"class":140,"line":280},[239,697,698],{"class":244},"\u002F\u002F trade-off: getting the define wrong (or forgetting it) leaves dev-only\n",[239,700,701],{"class":140,"line":372},[239,702,703],{"class":244},"\u002F\u002F warning code in the production bundle, inflating bytes and parse time.\n",[32,705,707],{"id":706},"step-by-step-benchmark-decide-and-switch","Step-by-step: benchmark, decide, and switch",[14,709,710],{},"Do not adopt a minifier on reputation. Run this ordered workflow on your real bundle; each step names the expected outcome so you know whether it was worth it.",[712,713,714,736,748,761,781,798],"ol",{},[43,715,716,719,720,723,724,727,728,731,732,735],{},[46,717,718],{},"Produce a stable pre-minify bundle."," Build once with minification disabled so both tools consume identical input: ",[51,721,722],{},"vite build --minify=false"," or webpack with ",[51,725,726],{},"optimization.minimize=false",". ",[225,729,730],{},"Expected outcome:"," one deterministic ",[51,733,734],{},"app.bundle.js"," you can feed to each minifier, removing bundler-version noise from the comparison.",[43,737,738,741,742,744,745,747],{},[46,739,740],{},"Benchmark wall-clock time."," Run the ",[51,743,252],{}," commands from the speed section, median of three, on a warm cache. ",[225,746,730],{}," a concrete ratio — typically esbuild finishing 20-100x faster. If minify was adding seconds to your watch loop or CI, that time is now recovered every build.",[43,749,750,753,754,757,758,760],{},[46,751,752],{},"Compare gzipped output, not raw."," Users download compressed bytes, so measure post-gzip: ",[51,755,756],{},"gzip -c out.esbuild.js | wc -c"," versus the same for Terser. ",[225,759,730],{}," a gap of roughly 1-3%, usually single-digit kilobytes on a 150KB route — quantify it before deciding it matters.",[43,762,763,766,767,770,771,774,775,777,778,780],{},[46,764,765],{},"Set production defines before judging DCE."," Pass ",[51,768,769],{},"--define:process.env.NODE_ENV='\"production\"'"," (esbuild) or the equivalent ",[51,772,773],{},"DefinePlugin"," so dev-only branches collapse to ",[51,776,517],{}," and both tools can drop them. ",[225,779,730],{}," dev-warning code disappears from both outputs, so any remaining size gap reflects real minifier depth rather than a missed define.",[43,782,783,786,787,789,790,485,792,794,795,797],{},[46,784,785],{},"Guard identifier names your runtime needs."," If step benchmarks show broken DI resolution, decorator metadata, or unreadable error stacks, add ",[51,788,416],{}," (esbuild) or scoped ",[51,791,406],{},[51,793,402],{}," (Terser). ",[225,796,730],{}," correctness restored at a small, measured size cost — preserve only the patterns you reflect on, not everything.",[43,799,800,803,804,807,808,811,812,815,816,818,819,822,823,727,825,827],{},[46,801,802],{},"Wire the winner into the bundler."," For Vite, esbuild is already the default (",[51,805,806],{},"build.minify: 'esbuild'","); to force Terser set ",[51,809,810],{},"build.minify: 'terser'"," and install ",[51,813,814],{},"terser",". For webpack, keep ",[51,817,218],{}," or swap in ",[51,820,821],{},"ESBuildMinifyPlugin"," from ",[51,824,222],{},[225,826,730],{}," one config line, reproducible in CI, with no change to application code.",[230,829,831],{"className":312,"code":830,"language":314,"meta":235,"style":235},"\u002F\u002F vite.config.js — pin the minifier explicitly so the choice is reviewable\nexport default {\n  build: {\n    minify: 'esbuild', \u002F\u002F default; set to 'terser' only if step 3 proved it worth the time\n    \u002F\u002F trade-off: 'terser' needs `npm i -D terser` and multiplies minify time;\n    \u002F\u002F only pin it when your gzipped delta and hot-path budget both justify it.\n  },\n};\n",[51,832,833,838,848,853,866,871,876,884],{"__ignoreMap":235},[239,834,835],{"class":140,"line":241},[239,836,837],{"class":244},"\u002F\u002F vite.config.js — pin the minifier explicitly so the choice is reviewable\n",[239,839,840,842,845],{"class":140,"line":248},[239,841,527],{"class":251},[239,843,844],{"class":251}," default",[239,846,339],{"class":847},"seIZK",[239,849,850],{"class":140,"line":266},[239,851,852],{"class":255},"  build: {\n",[239,854,855,858,861,863],{"class":140,"line":274},[239,856,857],{"class":255},"    minify: ",[239,859,860],{"class":262},"'esbuild'",[239,862,403],{"class":255},[239,864,865],{"class":244},"\u002F\u002F default; set to 'terser' only if step 3 proved it worth the time\n",[239,867,868],{"class":140,"line":280},[239,869,870],{"class":244},"    \u002F\u002F trade-off: 'terser' needs `npm i -D terser` and multiplies minify time;\n",[239,872,873],{"class":140,"line":372},[239,874,875],{"class":244},"    \u002F\u002F only pin it when your gzipped delta and hot-path budget both justify it.\n",[239,877,878,881],{"class":140,"line":378},[239,879,880],{"class":255},"  }",[239,882,883],{"class":847},",\n",[239,885,886,889],{"class":140,"line":493},[239,887,888],{"class":847},"}",[239,890,891],{"class":255},";\n",[32,893,895],{"id":894},"verification-prove-the-delta-and-lock-it-in","Verification: prove the delta and lock it in",[14,897,898],{},"A one-off benchmark is not a decision you can defend six months later. Capture the before\u002Fafter and assert on it in CI so a future dependency bump cannot quietly erase the win.",[40,900,901,907,913],{},[43,902,903,906],{},[46,904,905],{},"Before\u002Fafter size diff."," Record gzipped route size under each minifier (e.g. esbuild 148KB vs Terser 145KB gzipped). If the delta is under a kilobyte or two on a route already inside the 150KB budget, the speed win makes esbuild the correct default and the diff itself is your evidence.",[43,908,909,912],{},[46,910,911],{},"Before\u002Fafter build time."," Note the minify wall-clock recovered per build; multiplied across a day of watch rebuilds and CI runs, this is usually the larger real-world saving.",[43,914,915,918,919,922,923,926],{},[46,916,917],{},"CI byte budget."," Add a ",[51,920,921],{},"size-limit"," (or ",[51,924,925],{},"bundlesize",") assertion on the gzipped route so any regression past your threshold fails the build — this matters far more than which minifier you picked, because it catches the dependency bloat that actually moves the number.",[230,928,932],{"className":929,"code":930,"language":931,"meta":235,"style":235},"language-json shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F package.json — fail CI if the gzipped route crosses budget, whatever the minifier\n{\n  \"size-limit\": [\n    { \"path\": \"dist\u002Fassets\u002Findex-*.js\", \"limit\": \"150 KB\", \"gzip\": true }\n  ],\n  \"scripts\": { \"size\": \"size-limit\" }\n  \u002F\u002F trade-off: too tight a limit turns every honest feature addition into a\n  \u002F\u002F red build; set it just above today's size and ratchet down deliberately.\n}\n","json",[51,933,934,939,944,953,989,994,1012,1017,1022],{"__ignoreMap":235},[239,935,936],{"class":140,"line":241},[239,937,938],{"class":244},"\u002F\u002F package.json — fail CI if the gzipped route crosses budget, whatever the minifier\n",[239,940,941],{"class":140,"line":248},[239,942,943],{"class":255},"{\n",[239,945,946,950],{"class":140,"line":266},[239,947,949],{"class":948},"s-fAs","  \"size-limit\"",[239,951,952],{"class":255},": [\n",[239,954,955,958,961,964,967,969,972,974,977,979,982,984,986],{"class":140,"line":274},[239,956,957],{"class":255},"    { ",[239,959,960],{"class":948},"\"path\"",[239,962,963],{"class":255},": ",[239,965,966],{"class":262},"\"dist\u002Fassets\u002Findex-*.js\"",[239,968,403],{"class":255},[239,970,971],{"class":948},"\"limit\"",[239,973,963],{"class":255},[239,975,976],{"class":262},"\"150 KB\"",[239,978,403],{"class":255},[239,980,981],{"class":948},"\"gzip\"",[239,983,963],{"class":255},[239,985,361],{"class":326},[239,987,988],{"class":255}," }\n",[239,990,991],{"class":140,"line":280},[239,992,993],{"class":255},"  ],\n",[239,995,996,999,1002,1005,1007,1010],{"class":140,"line":372},[239,997,998],{"class":948},"  \"scripts\"",[239,1000,1001],{"class":255},": { ",[239,1003,1004],{"class":948},"\"size\"",[239,1006,963],{"class":255},[239,1008,1009],{"class":262},"\"size-limit\"",[239,1011,988],{"class":255},[239,1013,1014],{"class":140,"line":378},[239,1015,1016],{"class":244},"  \u002F\u002F trade-off: too tight a limit turns every honest feature addition into a\n",[239,1018,1019],{"class":140,"line":493},[239,1020,1021],{"class":244},"  \u002F\u002F red build; set it just above today's size and ratchet down deliberately.\n",[239,1023,1024],{"class":140,"line":499},[239,1025,1026],{"class":255},"}\n",[14,1028,1029,1030,1034],{},"Once the budget assertion is green, close the loop in the field: smaller, faster-parsing bundles show up as lower interaction latency in RUM, which is the point of the whole exercise — see ",[18,1031,1033],{"href":1032},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F","improving First Input Delay and INP"," for how to confirm the responsiveness gain against the 200ms INP threshold.",[32,1036,1038],{"id":1037},"when-to-pick-which","When to pick which",[1040,1041,1042,1057],"table",{},[1043,1044,1045],"thead",{},[1046,1047,1048,1053,1055],"tr",{},[1049,1050,1052],"th",{"align":1051},"left","Criterion",[1049,1054,133],{"align":1051},[1049,1056,138],{"align":1051},[1058,1059,1060,1071,1082,1100,1111],"tbody",{},[1046,1061,1062,1065,1068],{},[1063,1064,149],"td",{"align":1051},[1063,1066,1067],{"align":1051},"20-100x faster",[1063,1069,1070],{"align":1051},"Baseline (slowest)",[1046,1072,1073,1076,1079],{},[1063,1074,1075],{"align":1051},"Output size (post-gzip)",[1063,1077,1078],{"align":1051},"Within ~1-3%",[1063,1080,1081],{"align":1051},"Smallest",[1046,1083,1084,1087,1092],{},[1063,1085,1086],{"align":1051},"Mangling safety controls",[1063,1088,1089,1091],{"align":1051},[51,1090,416],{},", fewer dials",[1063,1093,1094,1095,485,1097],{"align":1051},"Granular ",[51,1096,409],{},[51,1098,1099],{},"keep_*",[1046,1101,1102,1105,1108],{},[1063,1103,1104],{"align":1051},"Dead-code elimination",[1063,1106,1107],{"align":1051},"Single-pass",[1063,1109,1110],{"align":1051},"Multi-pass",[1046,1112,1113,1116,1119],{},[1063,1114,1115],{"align":1051},"Default in tooling",[1063,1117,1118],{"align":1051},"Vite default; webpack opt-in",[1063,1120,1121],{"align":1051},"webpack default",[14,1123,1124,1127],{},[46,1125,1126],{},"Pick esbuild"," for almost every app. The build-speed win is enormous, the output is within a few percent of Terser after gzip, and the safe defaults reduce the chance of a mangling bug. This is the right default for new projects and for any team that values fast iteration.",[14,1129,1130,1133],{},[46,1131,1132],{},"Pick Terser"," when you are genuinely byte-constrained on a hot path (a tiny embeddable widget, an ads-budget-sensitive script) and have measured that its multi-pass output meaningfully beats esbuild for your code, or when you need its granular name-preservation controls for a framework or error-reporting tool that reflects on identifiers. For the rare bundle where the last 1-2% is contested and tested, Terser's extra passes earn their time.",[14,1135,1136],{},"The meta-point: the minifier is a small lever. Spend your effort where the kilobytes actually live — dependency selection, tree-shaking hygiene, and code-split boundaries — and let a fast, safe minifier handle the final polish.",[32,1138,1140],{"id":1139},"related","Related",[40,1142,1143,1149,1156,1161,1166],{},[43,1144,1145,1148],{},[18,1146,1147],{"href":20},"Tree-shaking and dead code elimination"," — the parent guide; minification only polishes what the bundler already pruned.",[43,1150,1151,1155],{},[18,1152,1154],{"href":1153},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Ffixing-tree-shaking-issues-with-lodash-and-moment\u002F","Fixing tree-shaking issues with lodash and moment"," — recovering far more bytes than any minifier switch.",[43,1157,1158,1160],{},[18,1159,539],{"href":538}," — how each bundler wires up its default minifier.",[43,1162,1163,1165],{},[18,1164,26],{"href":25}," — the end-to-end payload-reduction workflow.",[43,1167,1168,1171],{},[18,1169,1170],{"href":1032},"Optimizing First Input Delay and INP"," — why smaller minified bundles cut parse time and improve responsiveness.",[1173,1174,1176],"script",{"type":1175},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"TechArticle\",\n      \"headline\": \"esbuild vs Terser for production minification\",\n      \"description\": \"A decision matrix comparing esbuild and Terser on minification speed, compression ratio, mangling safety, and dead-code elimination, with a benchmark workflow and CI byte-budget guardrail.\",\n      \"datePublished\": \"2026-06-18\",\n      \"dateModified\": \"2026-07-18\",\n      \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002F\"\n    },\n    {\n      \"@type\": \"HowTo\",\n      \"name\": \"Benchmark and choose between esbuild and Terser for minification\",\n      \"step\": [\n        { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Produce a stable pre-minify bundle\", \"text\": \"Build once with minification disabled so both tools consume identical input.\" },\n        { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Benchmark minify speed\", \"text\": \"Minify the same bundle with both tools three times and compare median wall-clock time.\" },\n        { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Compare gzipped output\", \"text\": \"Measure the gzipped size difference; it is usually within 1-3%.\" },\n        { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Set production defines before judging DCE\", \"text\": \"Set NODE_ENV defines so both minifiers can drop dev-only branches, isolating real depth differences.\" },\n        { \"@type\": \"HowToStep\", \"position\": 5, \"name\": \"Guard identifier names\", \"text\": \"Preserve only the class\u002Ffunction names your runtime reflects on, using --keep-names or scoped keep_* options.\" },\n        { \"@type\": \"HowToStep\", \"position\": 6, \"name\": \"Wire the winner into the bundler and lock a byte budget\", \"text\": \"Pin the minifier in bundler config and add a size-limit CI assertion on the gzipped route.\" }\n      ]\n    },\n    {\n      \"@type\": \"BreadcrumbList\",\n      \"itemListElement\": [\n        { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"JavaScript Bundle Optimization & Code Splitting\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Tree Shaking & Dead Code Elimination\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"esbuild vs Terser for production minification\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002F\" }\n      ]\n    }\n  ]\n}\n",[1178,1179,1180],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}html pre.shiki code .s-fAs, html code.shiki .s-fAs{--shiki-default:#024C1A;--shiki-dark:#024C1A;--shiki-light:#024C1A}",{"title":235,"searchDepth":248,"depth":248,"links":1182},[1183,1184,1185,1191,1192,1193,1194],{"id":34,"depth":248,"text":35},{"id":88,"depth":248,"text":89},{"id":200,"depth":248,"text":201,"children":1186},[1187,1188,1189,1190],{"id":208,"depth":266,"text":209},{"id":286,"depth":266,"text":287},{"id":384,"depth":266,"text":385},{"id":510,"depth":266,"text":511},{"id":706,"depth":248,"text":707},{"id":894,"depth":248,"text":895},{"id":1037,"depth":248,"text":1038},{"id":1139,"depth":248,"text":1140},"A decision matrix comparing esbuild and Terser on minification speed, compression ratio, mangling safety, and dead-code elimination — with a benchmark workflow and CI guardrail.","md",{"slug":12,"type":1198,"breadcrumb":1199,"datePublished":1208,"dateModified":1209},"article",[1200,1202,1204,1206],{"name":1201,"url":485},"Home",{"name":1203,"url":25},"JavaScript Bundle Optimization & Code Splitting",{"name":1205,"url":20},"Tree Shaking & Dead Code Elimination",{"name":5,"url":1207},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002F","2026-06-18","2026-07-18",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification",{"title":1213,"description":1214},"esbuild vs Terser for Production Minification","Compare esbuild and Terser for production JS minification: speed, compression ratio, mangling safety, and dead-code elimination, plus when to pick which.","javascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002Findex","XrG3Pu076qrUj-D4Rb-4OLBCOubWgDsY7z_HAZCsEak",[1218,1222],{"title":1219,"path":1220,"stem":1221},"Tree Shaking and Dead Code Elimination","\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination","javascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Findex",{"title":1223,"path":1224,"stem":1225},"Fixing Tree Shaking Issues with Lodash and Moment","\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Ffixing-tree-shaking-issues-with-lodash-and-moment","javascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Ffixing-tree-shaking-issues-with-lodash-and-moment\u002Findex",1784398533805]