diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8c65f4572..1c3a061cf33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,25 @@ And the reason lowering private members requires adjusting `super()` calls is because the injected private member initializers use `this`, which is only accessible after `super()` calls in the constructor. +* Print some large integers using hexadecimal when minifying ([#2162](https://github.com/evanw/esbuild/issues/2162)) + + When `--minify` is active, esbuild will now use one fewer byte to represent certain large integers: + + ```js + // Original code + x = 123456787654321; + + // Old output (with --minify) + x=123456787654321; + + // New output (with --minify) + x=0x704885f926b1; + ``` + + This works because a hexadecimal representation can be shorter than a decimal representation starting at around 1012 and above. + + _This optimization made me realize that there's probably an opportunity to optimize printed numbers for smaller gzipped size instead of or in addition to just optimizing for minimal uncompressed byte count. The gzip algorithm does better with repetitive sequences, so for example `0xFFFFFFFF` is probably a better representation than `4294967295` even though the byte counts are the same. As far as I know, no JavaScript minifier does this optimization yet. I don't know enough about how gzip works to know if this is a good idea or what the right metric for this might be._ + * Add Linux ARM64 support for Deno ([#2156](https://github.com/evanw/esbuild/issues/2156)) This release adds Linux ARM64 support to esbuild's [Deno](https://deno.land/) API implementation, which allows esbuild to be used with Deno on a Raspberry Pi. diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index ef90a55b3bd..018908b6478 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -2788,6 +2788,15 @@ func (p *printer) printNonNegativeFloat(absValue float64) { } } + // Numbers in this range can potentially be printed with one fewer byte as hex + if p.options.MinifyWhitespace && absValue >= 1_000_000_000_000 && absValue <= 0xFFFF_FFFF_FFFF_FFFF { + if asInt := uint64(absValue); absValue == float64(asInt) { + if hex := strconv.FormatUint(asInt, 16); 2+len(hex) < len(result) { + result = append(append(result[:0], '0', 'x'), hex...) + } + } + } + p.printBytes(result) } diff --git a/internal/js_printer/js_printer_test.go b/internal/js_printer/js_printer_test.go index b436231600d..3d88f4ae24c 100644 --- a/internal/js_printer/js_printer_test.go +++ b/internal/js_printer/js_printer_test.go @@ -262,6 +262,16 @@ func TestNumber(t *testing.T) { expectPrinted(t, "x = -0xffff_ffff_ffff_fbff", "x = -1844674407370955e4;\n") expectPrinted(t, "x = -0x1_0000_0000_0000_0000", "x = -18446744073709552e3;\n") expectPrinted(t, "x = -0x1_0000_0000_0000_1000", "x = -18446744073709556e3;\n") + + // Check the hex vs. decimal decision boundary when minifying + expectPrinted(t, "x = 999999999999", "x = 999999999999;\n") + expectPrinted(t, "x = 1000000000001", "x = 1000000000001;\n") + expectPrinted(t, "x = 0xFFFFFFFFFFFFF80", "x = 1152921504606846800;\n") + expectPrinted(t, "x = 0x1000000000000000", "x = 1152921504606847e3;\n") + expectPrintedMinify(t, "x = 999999999999", "x=999999999999;") + expectPrintedMinify(t, "x = 1000000000001", "x=0xe8d4a51001;") + expectPrintedMinify(t, "x = 0xFFFFFFFFFFFFF80", "x=0xfffffffffffff80;") + expectPrintedMinify(t, "x = 0x1000000000000000", "x=1152921504606847e3;") } func TestArray(t *testing.T) {