Skip to content

Commit

Permalink
minify unused ${x}y into ${x} instead of x+""
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 15, 2021
1 parent eef9253 commit 8244508
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 23 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
""+1+2+3,""+(x?1:2)+y;

// New output (with --minify)
x,""+y;
x,`${y}`;
```

This can arise when the template literals are nested inside of another function call that was determined to be unnecessary such as an unused call to a function marked with the `/* @__PURE__ */` pragma.

This release also fixes a bug with this transformation where minifying the unused expression `` `foo ${bar}` `` into `"" + bar` changed the meaning of the expression. Template string interpolation always calls `toString` while string addition may call `valueOf` instead. This unused expression is now minified to `` `${bar}` ``, which is slightly longer but which avoids the behavior change.

## 0.14.5

* Fix an issue with the publishing script
Expand Down
31 changes: 17 additions & 14 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14218,29 +14218,32 @@ func (p *parser) simplifyUnusedExpr(expr js_ast.Expr) js_ast.Expr {
case *js_ast.ETemplate:
if e.TagOrNil.Data == nil {
var comma js_ast.Expr
var concat js_ast.Expr
var templateLoc logger.Loc
var template *js_ast.ETemplate
for _, part := range e.Parts {
// If we know this value is some kind of primitive, then we know that "ToString" has no side effects
// If we know this value is some kind of primitive, then we know that
// "ToString" has no side effects and can be avoided.
if js_ast.KnownPrimitiveType(part.Value) != js_ast.PrimitiveUnknown {
if concat.Data != nil {
comma = js_ast.JoinWithComma(comma, concat)
concat.Data = nil
if template != nil {
comma = js_ast.JoinWithComma(comma, js_ast.Expr{Loc: templateLoc, Data: template})
template = nil
}
comma = js_ast.JoinWithComma(comma, p.simplifyUnusedExpr(part.Value))
continue
}

// Make sure "ToString" is still evaluated on the value
if concat.Data == nil {
concat = js_ast.Expr{Loc: part.Value.Loc, Data: &js_ast.EString{}}
// Make sure "ToString" is still evaluated on the value. We can't use
// string addition here because that may evaluate "ValueOf" instead.
if template == nil {
template = &js_ast.ETemplate{}
templateLoc = part.Value.Loc
}
concat = js_ast.Expr{Loc: part.Value.Loc, Data: &js_ast.EBinary{
Op: js_ast.BinOpAdd,
Left: concat,
Right: part.Value,
}}
template.Parts = append(template.Parts, js_ast.TemplatePart{Value: part.Value})
}
if template != nil {
comma = js_ast.JoinWithComma(comma, js_ast.Expr{Loc: templateLoc, Data: template})
}
return js_ast.JoinWithComma(comma, concat)
return comma
}

case *js_ast.EArray:
Expand Down
18 changes: 10 additions & 8 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3813,14 +3813,16 @@ func TestMangleUnused(t *testing.T) {
}

expectPrintedMangle(t, "tag`a${b}c${d}e`", "tag`a${b}c${d}e`;\n")
expectPrintedMangle(t, "`a${b}c${d}e`", "\"\" + b + d;\n")

expectPrintedMangle(t, "`${x}${1}`", "\"\" + x;\n")
expectPrintedMangle(t, "`${1}${y}`", "\"\" + y;\n")
expectPrintedMangle(t, "`${x}${y}`", "\"\" + x + y;\n")
expectPrintedMangle(t, "`${x ? 1 : 2}${y}`", "x, \"\" + y;\n")
expectPrintedMangle(t, "`${x}${y ? 1 : 2}`", "\"\" + x, y;\n")
expectPrintedMangle(t, "`${x}${y ? 1 : 2}${z}`", "\"\" + x, y, \"\" + z;\n")
expectPrintedMangle(t, "`a${b}c${d}e`", "`${b}${d}`;\n")

// These can't be reduced to string addition due to "valueOf". See:
// https://github.com/terser/terser/issues/1128#issuecomment-994209801
expectPrintedMangle(t, "`stuff ${x} ${1}`", "`${x}`;\n")
expectPrintedMangle(t, "`stuff ${1} ${y}`", "`${y}`;\n")
expectPrintedMangle(t, "`stuff ${x} ${y}`", "`${x}${y}`;\n")
expectPrintedMangle(t, "`stuff ${x ? 1 : 2} ${y}`", "x, `${y}`;\n")
expectPrintedMangle(t, "`stuff ${x} ${y ? 1 : 2}`", "`${x}`, y;\n")
expectPrintedMangle(t, "`stuff ${x} ${y ? 1 : 2} ${z}`", "`${x}`, y, `${z}`;\n")

expectPrintedMangle(t, "'a' + b + 'c' + d", "\"\" + b + d;\n")
expectPrintedMangle(t, "a + 'b' + c + 'd'", "a + \"\" + c;\n")
Expand Down
24 changes: 24 additions & 0 deletions scripts/end-to-end-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,30 @@
if (foo() === bar()) throw 'fail'
`,
}),

// Unused minified template literals. See this for more info:
// https://github.com/terser/terser/issues/1128#issuecomment-994209801
test(['in.js', '--outfile=node.js', '--minify', target], {
'in.js': `
var text = '';
var foo = {
toString: () => text += 'toString',
valueOf: () => text += 'valueOf',
};
\`\${foo}\`;
if (text !== 'toString') throw 'fail: ' + text + ' !== toString'
`,
}),
test(['in.js', '--outfile=node.js', '--minify', target], {
'in.js': `
var text = '';
var foo = {
toString: () => text += 'toString',
};
\`abc \${text += 'A', foo} xyz \${text += 'B', foo} 123\`;
if (text !== 'AtoStringBtoString') throw 'fail: ' + text + ' !== AtoStringBtoString'
`,
}),
)
}

Expand Down

0 comments on commit 8244508

Please sign in to comment.