Skip to content

Commit

Permalink
minifier: inline String.fromCharCode() with ints
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 17, 2023
1 parent 6d20a48 commit 126ca5a
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 4 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## Unreleased

* Add some improvements to the JavaScript minifier

This release adds more cases to the JavaScript minifier, including support for inlining `String.fromCharCode` and `String.prototype.charCodeAt` when possible:

```js
// Original code
document.onkeydown = e => e.keyCode === 'A'.charCodeAt(0) && console.log(String.fromCharCode(55358, 56768))

// Old output (with --minify)
document.onkeydown=o=>o.keyCode==="A".charCodeAt(0)&&console.log(String.fromCharCode(55358,56768));

// New output (with --minify)
document.onkeydown=o=>o.keyCode===65&&console.log("🧀");
```

## 0.19.5

* Fix a regression in 0.19.0 regarding `paths` in `tsconfig.json` ([#3354](https://github.com/evanw/esbuild/issues/3354))
Expand Down
23 changes: 20 additions & 3 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14554,9 +14554,9 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
}
}

// Recognize "charCodeAt()" calls
if p.options.minifySyntax && len(e.Args) <= 1 {
if t.Name == "charCodeAt" {
if p.options.minifySyntax {
if len(e.Args) <= 1 && t.Name == "charCodeAt" {
// Recognize "charCodeAt()" calls
if str, ok := t.Target.Data.(*js_ast.EString); ok {
index := 0
hasIndex := false
Expand All @@ -14574,6 +14574,23 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
}
}
}
} else if t.Name == "fromCharCode" {
// Recognize "fromCharCode()" calls
if id, ok := t.Target.Data.(*js_ast.EIdentifier); ok {
if symbol := &p.symbols[id.Ref.InnerIndex]; symbol.Kind == ast.SymbolUnbound && symbol.OriginalName == "String" {
charCodes := make([]uint16, 0, len(e.Args))
for _, arg := range e.Args {
arg, ok := js_ast.ToNumberWithoutSideEffects(arg.Data)
if !ok {
break
}
charCodes = append(charCodes, uint16(js_ast.ToInt32(arg)))
}
if len(charCodes) == len(e.Args) {
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: charCodes}}, exprOut{}
}
}
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3581,6 +3581,26 @@ func TestMangleCharCodeAt(t *testing.T) {
expectPrintedMangle(t, "a = 'xy'.charCodeAt(1, 2)", "a = \"xy\".charCodeAt(1, 2);\n")
}

func TestMangleFromCharCode(t *testing.T) {
expectPrinted(t, "a = String.fromCharCode(120, 121)", "a = String.fromCharCode(120, 121);\n")

expectPrintedMangle(t, "a = String.fromCharCode()", "a = \"\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(0)", "a = \"\\0\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(120)", "a = \"x\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(120, 121)", "a = \"xy\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(55358, 56768)", "a = \"🧀\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(0x10000)", "a = \"\\0\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(0x10078, 0x10079)", "a = \"xy\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(0x1_0000_FFFF)", "a = \"\uFFFF\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(NaN)", "a = \"\\0\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(Infinity)", "a = \"\\0\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(null)", "a = \"\\0\";\n")
expectPrintedMangle(t, "a = String.fromCharCode(undefined)", "a = \"\\0\";\n")

expectPrintedMangle(t, "a = String.fromCharCode(x)", "a = String.fromCharCode(x);\n")
expectPrintedMangle(t, "a = String.fromCharCode('123')", "a = String.fromCharCode(\"123\");\n")
}

func TestMangleIf(t *testing.T) {
expectPrintedNormalAndMangle(t, "1 ? a() : b()", "1 ? a() : b();\n", "a();\n")
expectPrintedNormalAndMangle(t, "0 ? a() : b()", "0 ? a() : b();\n", "b();\n")
Expand Down
2 changes: 1 addition & 1 deletion scripts/end-to-end-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3242,7 +3242,7 @@ for (const minify of [[], ['--minify-syntax']]) {
test(['in.js', '--outfile=node.js', '--log-level=error'].concat(minify), {
'in.js': `if ({a: 1, a: 2}${access} !== 2) throw 'fail'`,
}),
test(['in.js', '--outfile=node.js'].concat(minify), {
test(['in.js', '--outfile=node.js', '--log-level=error'].concat(minify), {
'in.js': `if ({a: 1, [String.fromCharCode(97)]: 2}${access} !== 2) throw 'fail'`,
}),
test(['in.js', '--outfile=node.js'].concat(minify), {
Expand Down

0 comments on commit 126ca5a

Please sign in to comment.