diff --git a/CHANGELOG.md b/CHANGELOG.md
index 744da2a53e..861c9cb811 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@
- Fix signature matching for externals when abstract alias hides function arity. https://github.com/rescript-lang/rescript/pull/8045
- Fix arity detection for arrows returning nested generics. https://github.com/rescript-lang/rescript/pull/8064
- Fix error handling when rescript.json parsing fails and improve error message. https://github.com/rescript-lang/rescript/pull/8067
+- Fix invalid JSX being generated for empty fragments. https://github.com/rescript-lang/rescript/pull/8077
#### :memo: Documentation
diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml
index ea1c3d5fda..43967a3f1c 100644
--- a/compiler/core/js_dump.ml
+++ b/compiler/core/js_dump.ml
@@ -1105,6 +1105,14 @@ and print_indented_list (f : P.t) (parent_expr_level : int) (cxt : cxt)
and print_jsx cxt ?(spread_props : J.expression option)
?(key : J.expression option) ~(level : int) f (fnName : string)
(tag : J.expression) (fields : (string * J.expression) list) : cxt =
+ (* TODO: make fragment detection respect custom JSX runtime modules instead of
+ assuming "JsxRuntime". *)
+ let is_fragment =
+ match tag.expression_desc with
+ | J.Var (J.Qualified ({id = {name = "JsxRuntime"}}, Some "Fragment")) ->
+ true
+ | _ -> false
+ in
let print_tag cxt =
match tag.expression_desc with
(* "div" or any other primitive tag *)
@@ -1112,7 +1120,7 @@ and print_jsx cxt ?(spread_props : J.expression option)
P.string f txt;
cxt
(* fragment *)
- | J.Var (J.Qualified ({id = {name = "JsxRuntime"}}, Some "Fragment")) -> cxt
+ | _ when is_fragment -> cxt
(* A user defined component or external component *)
| _ -> expression ~level cxt f tag
in
@@ -1129,6 +1137,11 @@ and print_jsx cxt ?(spread_props : J.expression option)
else Some [e]
else None)
fields
+ (* For fragments without children we normalize to an empty list so they
+ print as <>> instead of > which is invalid JSX. *)
+ |> function
+ | None when is_fragment -> Some []
+ | other -> other
in
let print_props cxt props =
(* If a key is present, should be printed before the spread props,
@@ -1169,7 +1182,7 @@ and print_jsx cxt ?(spread_props : J.expression option)
match children_opt with
| Some _ -> cxt
| None ->
- (* Put a space the tag name and /> *)
+ (* Put a space between the tag name and /> *)
P.space f;
cxt)
else
diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs
index 3622e7901d..570ca4607c 100644
--- a/tests/tests/src/jsx_preserve_test.mjs
+++ b/tests/tests/src/jsx_preserve_test.mjs
@@ -192,6 +192,8 @@ let _props_with_hyphen = ;
+let _empty_fragment = <>>;
+
let _fragment = <>
{"Hello, world!"}
>;
@@ -271,6 +273,7 @@ export {
ComponentWithOptionalProps,
_optional_props,
_props_with_hyphen,
+ _empty_fragment,
_fragment,
_youtube_iframe,
X,
diff --git a/tests/tests/src/jsx_preserve_test.res b/tests/tests/src/jsx_preserve_test.res
index e5900a7fdd..aeeaad1531 100644
--- a/tests/tests/src/jsx_preserve_test.res
+++ b/tests/tests/src/jsx_preserve_test.res
@@ -124,6 +124,8 @@ let _optional_props = }
let _props_with_hyphen =
+let _empty_fragment = <> >
+
let _fragment = <> {Jsx.string("Hello, world!")} >
let _youtube_iframe =