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