Skip to content

Commit 7aa47c3

Browse files
committed
fix #4028: minify live/dead switch cases better
1 parent 22ecd30 commit 7aa47c3

File tree

5 files changed

+449
-2
lines changed

5 files changed

+449
-2
lines changed

CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,29 @@
166166

167167
The `--resolve-extensions=` option lets you specify the order in which to try resolving implicit file extensions. For complicated reasons, esbuild reorders TypeScript file extensions after JavaScript ones inside of `node_modules` so that JavaScript source code is always preferred to TypeScript source code inside of dependencies. However, this reordering had a bug that could accidentally change the relative order of TypeScript file extensions if one of them was a prefix of the other. That bug has been fixed in this release. You can see the issue for details.
168168

169+
* Better minification of statically-determined `switch` cases ([#4028](https://github.com/evanw/esbuild/issues/4028))
170+
171+
With this release, esbuild will now try to trim unused code within `switch` statements when the test expression and `case` expressions are primitive literals. This can arise when the test expression is an identifier that is substituted for a primitive literal at compile time. For example:
172+
173+
```js
174+
// Original code
175+
switch (MODE) {
176+
case 'dev':
177+
installDevToolsConsole()
178+
break
179+
case 'prod':
180+
return
181+
default:
182+
throw new Error
183+
}
184+
185+
// Old output (with --minify '--define:MODE="prod"')
186+
switch("prod"){case"dev":installDevToolsConsole();break;case"prod":return;default:throw new Error}
187+
188+
// New output (with --minify '--define:MODE="prod"')
189+
return;
190+
```
191+
169192
* Emit `/* @__KEY__ */` for string literals derived from property names ([#4034](https://github.com/evanw/esbuild/issues/4034))
170193

171194
Property name mangling is an advanced feature that shortens certain property names for better minification (I say "advanced feature" because it's very easy to break your code with it). Sometimes you need to store a property name in a string, such as `obj.get('foo')` instead of `obj.foo`. JavaScript minifiers such as esbuild and [Terser](https://terser.org/) have a convention where a `/* @__KEY__ */` comment before the string makes it behave like a property name. So `obj.get(/* @__KEY__ */ 'foo')` allows the contents of the string `'foo'` to be shortened.

internal/bundler_tests/bundler_dce_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,89 @@ func TestDeadCodeInsideEmptyTry(t *testing.T) {
14301430
})
14311431
}
14321432

1433+
func TestDeadCodeInsideUnusedCases(t *testing.T) {
1434+
dce_suite.expectBundled(t, bundled{
1435+
files: map[string]string{
1436+
"/entry.js": `
1437+
// Unknown test value
1438+
switch (x) {
1439+
case 0: _ = require('./a'); break
1440+
case 1: _ = require('./b'); break
1441+
}
1442+
1443+
// Known test value
1444+
switch (1) {
1445+
case 0: _ = require('./FAIL-known-0'); break
1446+
case 1: _ = require('./a'); break
1447+
case 1: _ = require('./FAIL-known-1'); break
1448+
case 2: _ = require('./FAIL-known-2'); break
1449+
}
1450+
1451+
// Check for "default"
1452+
switch (1) {
1453+
case 1: _ = require('./a'); break
1454+
default: _ = require('./FAIL-default'); break
1455+
}
1456+
switch (0) {
1457+
case 1: _ = require('./FAIL-default-1'); break
1458+
default: _ = require('./a'); break
1459+
case 0: _ = require('./FAIL-default-0'); break
1460+
}
1461+
1462+
// Check for non-constant cases
1463+
switch (1) {
1464+
case x: _ = require('./a'); break
1465+
case 1: _ = require('./b'); break
1466+
case x: _ = require('./FAIL-x'); break
1467+
default: _ = require('./FAIL-x-default'); break
1468+
}
1469+
1470+
// Check for other kinds of jumps
1471+
for (const x of y)
1472+
switch (1) {
1473+
case 0: _ = require('./FAIL-continue-0'); continue
1474+
case 1: _ = require('./a'); continue
1475+
case 2: _ = require('./FAIL-continue-2'); continue
1476+
}
1477+
x = () => {
1478+
switch (1) {
1479+
case 0: _ = require('./FAIL-return-0'); return
1480+
case 1: _ = require('./a'); return
1481+
case 2: _ = require('./FAIL-return-2'); return
1482+
}
1483+
}
1484+
1485+
// Check for fall-through
1486+
switch ('b') {
1487+
case 'a': _ = require('./FAIL-fallthrough-a')
1488+
case 'b': _ = require('./a')
1489+
case 'c': _ = require('./b'); break
1490+
case 'd': _ = require('./FAIL-fallthrough-d')
1491+
}
1492+
switch ('b') {
1493+
case 'a': _ = require('./FAIL-fallthrough-a')
1494+
case 'b':
1495+
case 'c': _ = require('./a')
1496+
case 'd': _ = require('./b'); break
1497+
case 'e': _ = require('./FAIL-fallthrough-e')
1498+
}
1499+
`,
1500+
"/a.js": ``,
1501+
"/b.js": ``,
1502+
},
1503+
entryPaths: []string{"/entry.js"},
1504+
options: config.Options{
1505+
Mode: config.ModeBundle,
1506+
AbsOutputFile: "/out.js",
1507+
},
1508+
expectedScanLog: `entry.js: WARNING: This case clause will never be evaluated because it duplicates an earlier case clause
1509+
entry.js: NOTE: The earlier case clause is here:
1510+
entry.js: WARNING: This case clause will never be evaluated because it duplicates an earlier case clause
1511+
entry.js: NOTE: The earlier case clause is here:
1512+
`,
1513+
})
1514+
}
1515+
14331516
func TestRemoveTrailingReturn(t *testing.T) {
14341517
dce_suite.expectBundled(t, bundled{
14351518
files: map[string]string{

internal/bundler_tests/snapshots/snapshots_dce.txt

+120
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,126 @@ try {
930930
require_d();
931931
}
932932

933+
================================================================================
934+
TestDeadCodeInsideUnusedCases
935+
---------- /out.js ----------
936+
// a.js
937+
var require_a = __commonJS({
938+
"a.js"() {
939+
}
940+
});
941+
942+
// b.js
943+
var require_b = __commonJS({
944+
"b.js"() {
945+
}
946+
});
947+
948+
// entry.js
949+
switch (x) {
950+
case 0:
951+
_ = require_a();
952+
break;
953+
case 1:
954+
_ = require_b();
955+
break;
956+
}
957+
switch (1) {
958+
case 0:
959+
_ = null;
960+
break;
961+
case 1:
962+
_ = require_a();
963+
break;
964+
case 1:
965+
_ = null;
966+
break;
967+
case 2:
968+
_ = null;
969+
break;
970+
}
971+
switch (1) {
972+
case 1:
973+
_ = require_a();
974+
break;
975+
default:
976+
_ = null;
977+
break;
978+
}
979+
switch (0) {
980+
case 1:
981+
_ = null;
982+
break;
983+
default:
984+
_ = require_a();
985+
break;
986+
case 0:
987+
_ = null;
988+
break;
989+
}
990+
switch (1) {
991+
case x:
992+
_ = require_a();
993+
break;
994+
case 1:
995+
_ = require_b();
996+
break;
997+
case x:
998+
_ = null;
999+
break;
1000+
default:
1001+
_ = null;
1002+
break;
1003+
}
1004+
for (const x2 of y)
1005+
switch (1) {
1006+
case 0:
1007+
_ = null;
1008+
continue;
1009+
case 1:
1010+
_ = require_a();
1011+
continue;
1012+
case 2:
1013+
_ = null;
1014+
continue;
1015+
}
1016+
x = () => {
1017+
switch (1) {
1018+
case 0:
1019+
_ = null;
1020+
return;
1021+
case 1:
1022+
_ = require_a();
1023+
return;
1024+
case 2:
1025+
_ = null;
1026+
return;
1027+
}
1028+
};
1029+
switch ("b") {
1030+
case "a":
1031+
_ = null;
1032+
case "b":
1033+
_ = require_a();
1034+
case "c":
1035+
_ = require_b();
1036+
break;
1037+
case "d":
1038+
_ = null;
1039+
}
1040+
switch ("b") {
1041+
case "a":
1042+
_ = null;
1043+
case "b":
1044+
case "c":
1045+
_ = require_a();
1046+
case "d":
1047+
_ = require_b();
1048+
break;
1049+
case "e":
1050+
_ = null;
1051+
}
1052+
9331053
================================================================================
9341054
TestDisableTreeShaking
9351055
---------- /out.js ----------

0 commit comments

Comments
 (0)