diff --git a/internal/bundler_tests/bundler_dce_test.go b/internal/bundler_tests/bundler_dce_test.go index 76c406e09fb..87bdcf7fdc1 100644 --- a/internal/bundler_tests/bundler_dce_test.go +++ b/internal/bundler_tests/bundler_dce_test.go @@ -3260,6 +3260,94 @@ func TestCrossModuleConstantFoldingNumber(t *testing.T) { }) } +func TestCrossModuleConstantFoldingString(t *testing.T) { + dce_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/enum-constants.ts": ` + export enum x { + a = 'foo', + b = 'bar', + } + `, + "/enum-entry.ts": ` + import { x } from './enum-constants' + console.log([ + typeof x.b, + ], [ + x.a + x.b, + ], [ + x.a < x.b, + x.a > x.b, + x.a <= x.b, + x.a >= x.b, + x.a == x.b, + x.a != x.b, + x.a === x.b, + x.a !== x.b, + ], [ + x.a && x.b, + x.a || x.b, + x.a ?? x.b, + ]) + `, + + "/const-constants.js": ` + export const a = 'foo' + export const b = 'bar' + `, + "/const-entry.js": ` + import { a, b } from './const-constants' + console.log([ + typeof b, + ], [ + a + b, + ], [ + a < b, + a > b, + a <= b, + a >= b, + a == b, + a != b, + a === b, + a !== b, + ], [ + a && b, + a || b, + a ?? b, + ]) + `, + + "/nested-constants.ts": ` + export const a = 'foo' + export const b = 'bar' + export const c = 'baz' + export enum x { + a = 'FOO', + b = 'BAR', + c = 'BAZ', + } + `, + "/nested-entry.ts": ` + import { a, b, c, x } from './nested-constants' + console.log({ + 'should be foobarbaz': a + b + c, + 'should be FOOBARBAZ': x.a + x.b + x.c, + }) + `, + }, + entryPaths: []string{ + "/enum-entry.ts", + "/const-entry.js", + "/nested-entry.ts", + }, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputDir: "/out", + MinifySyntax: true, + }, + }) +} + func TestMultipleDeclarationTreeShaking(t *testing.T) { dce_suite.expectBundled(t, bundled{ files: map[string]string{ diff --git a/internal/bundler_tests/snapshots/snapshots_dce.txt b/internal/bundler_tests/snapshots/snapshots_dce.txt index 5a4e2587892..73b16354c78 100644 --- a/internal/bundler_tests/snapshots/snapshots_dce.txt +++ b/internal/bundler_tests/snapshots/snapshots_dce.txt @@ -341,6 +341,63 @@ console.log({ "should be 32": 32 }); +================================================================================ +TestCrossModuleConstantFoldingString +---------- /out/enum-entry.js ---------- +// enum-entry.ts +console.log([ + typeof "bar" /* b */ +], [ + "foobar" +], [ + !1, + !0, + !1, + !0, + !1, + !0, + !1, + !0 +], [ + "foo" /* a */ && "bar" /* b */, + "foo" /* a */ || "bar" /* b */, + "foo" /* a */ ?? "bar" /* b */ +]); + +---------- /out/const-entry.js ---------- +// const-constants.js +var a = "foo", b = "bar"; + +// const-entry.js +console.log([ + typeof b +], [ + a + b +], [ + a < b, + a > b, + a <= b, + a >= b, + a == b, + a != b, + a === b, + a !== b +], [ + a && b, + a || b, + a ?? b +]); + +---------- /out/nested-entry.js ---------- +// nested-constants.ts +var a = "foo", b = "bar", c = "baz"; + +// nested-entry.ts +console.log({ + "should be foobarbaz": a + b + c, + "should be FOOBARBAZ": "FOOBARBAZ" +}); + ================================================================================ TestDCEClassStaticBlocks ---------- /out.js ---------- diff --git a/internal/js_ast/js_ast_helpers.go b/internal/js_ast/js_ast_helpers.go index 285a79277a5..dd1d0a8de82 100644 --- a/internal/js_ast/js_ast_helpers.go +++ b/internal/js_ast/js_ast_helpers.go @@ -1167,6 +1167,11 @@ func ShouldFoldBinaryOperatorWhenMinifying(binary *EBinary) bool { return true } + // String addition should pretty much always be more compact when folded + if _, _, ok := extractStringValues(binary.Left, binary.Right); ok { + return true + } + case BinOpSub: // Subtraction of small-ish integers can definitely be folded without issues // "3 - 1" => "2" @@ -1215,6 +1220,9 @@ func FoldBinaryOperator(loc logger.Loc, e *EBinary) Expr { if left, right, ok := extractNumericValues(e.Left, e.Right); ok { return Expr{Loc: loc, Data: &ENumber{Value: left + right}} } + if left, right, ok := extractStringValues(e.Left, e.Right); ok { + return Expr{Loc: loc, Data: &EString{Value: joinStrings(left, right)}} + } case BinOpSub: if left, right, ok := extractNumericValues(e.Left, e.Right); ok { diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 06221966618..03cbed9d721 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -1758,17 +1758,22 @@ func (p *printer) lateConstantFoldUnaryOrBinaryExpr(expr js_ast.Expr) js_ast.Exp } case *js_ast.EDot: - if value, ok := p.tryToGetImportedEnumValue(e.Target, e.Name); ok && value.String == nil { - value := js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: value.Number}} + if value, ok := p.tryToGetImportedEnumValue(e.Target, e.Name); ok { + var inlinedValue js_ast.Expr + if value.String != nil { + inlinedValue = js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: value.String}} + } else { + inlinedValue = js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: value.Number}} + } if strings.Contains(e.Name, "*/") { // Don't wrap with a comment - return value + return inlinedValue } // Wrap with a comment - return js_ast.Expr{Loc: value.Loc, Data: &js_ast.EInlinedEnum{ - Value: value, + return js_ast.Expr{Loc: inlinedValue.Loc, Data: &js_ast.EInlinedEnum{ + Value: inlinedValue, Comment: e.Name, }} }