diff --git a/CHANGELOG.md b/CHANGELOG.md
index 000fb119f9f..f22b2887a6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## Unreleased
+
+* Fix crash when pretty-printing minified JSX with object spread of object literal with computed property ([#2697](https://github.com/evanw/esbuild/issues/2697))
+
+ JSX elements are translated to JavaScript function calls and JSX element attributes are translated to properties on a JavaScript object literal. These properties are always either strings (e.g. in ``, `y` is a string) or an object spread (e.g. in ``, `y` is an object spread) because JSX doesn't provide syntax for directly passing a computed property as a JSX attribute. However, esbuild's minifier has a rule that tries to inline object spread with an inline object literal in JavaScript. For example, `x = { ...{ y } }` is minified to `x={y}` when minification is enabled. This means that there is a way to generate a non-string non-spread JSX attribute in esbuild's internal representation. One example is with ``. When minification is enabled, esbuild's internal representation of this is something like `` due to object spread inlining, which is not valid JSX syntax. If this internal representation is then pretty-printed as JSX using `--minify --jsx=preserve`, esbuild previously crashed when trying to print this invalid syntax. With this release, esbuild will now print `` in this scenario instead of crashing.
+
## 0.15.15
* Remove duplicate CSS rules across files ([#2688](https://github.com/evanw/esbuild/issues/2688))
diff --git a/internal/bundler/bundler_default_test.go b/internal/bundler/bundler_default_test.go
index 9eb3c07522a..6ccf661287f 100644
--- a/internal/bundler/bundler_default_test.go
+++ b/internal/bundler/bundler_default_test.go
@@ -6777,3 +6777,42 @@ func TestNonDeterminismIssue2537(t *testing.T) {
},
})
}
+
+// See: https://github.com/evanw/esbuild/issues/2697
+func TestMinifiedJSXPreserveWithObjectSpread(t *testing.T) {
+ loader_suite.expectBundled(t, bundled{
+ files: map[string]string{
+ "/entry.jsx": `
+ const obj = {
+ before,
+ ...{ [key]: value },
+ ...{ key: value },
+ after,
+ };
+ ;
+ ;
+ `,
+ },
+ entryPaths: []string{"/entry.jsx"},
+ options: config.Options{
+ Mode: config.ModeBundle,
+ AbsOutputFile: "/out.js",
+ MinifySyntax: true,
+ JSX: config.JSXOptions{
+ Preserve: true,
+ },
+ },
+ })
+}
diff --git a/internal/bundler/snapshots/snapshots_loader.txt b/internal/bundler/snapshots/snapshots_loader.txt
index 7e5420e9f56..4d4e3466fd2 100644
--- a/internal/bundler/snapshots/snapshots_loader.txt
+++ b/internal/bundler/snapshots/snapshots_loader.txt
@@ -922,6 +922,29 @@ x.a, x?.a, x[y ? "a" : z], x?.[y ? "a" : z], x[y ? z : "a"], x?.[y ? z : "a"], x
var { a: x } = y, { ["a"]: x } = y, { [(z, "a")]: x } = y;
"a" in x, (y ? "a" : z) in x, (y ? z : "a") in x, y, "a" in x;
+================================================================================
+TestMinifiedJSXPreserveWithObjectSpread
+---------- /out.js ----------
+// entry.jsx
+var obj = {
+ before,
+ [key]: value,
+ key: value,
+ after
+};
+;
+;
+
================================================================================
TestMinifyIdentifiersImportPathFrequencyAnalysis
---------- /out/import.js ----------
diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go
index eefea3966e3..68228f6988c 100644
--- a/internal/js_printer/js_printer.go
+++ b/internal/js_printer/js_printer.go
@@ -1619,9 +1619,20 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
name := p.mangledPropName(mangled.Ref)
p.addSourceMappingForName(property.Key.Loc, name, mangled.Ref)
p.printIdentifier(name)
- } else {
+ } else if str, ok := property.Key.Data.(*js_ast.EString); ok {
p.addSourceMapping(property.Key.Loc)
- p.print(helpers.UTF16ToString(property.Key.Data.(*js_ast.EString).Value))
+ p.print(helpers.UTF16ToString(str.Value))
+ } else {
+ p.print("{...{")
+ p.printSpace()
+ p.print("[")
+ p.printExpr(property.Key, js_ast.LComma, 0)
+ p.print("]:")
+ p.printSpace()
+ p.printExpr(property.ValueOrNil, js_ast.LComma, 0)
+ p.printSpace()
+ p.print("}}")
+ continue
}
// Special-case string values