@@ -515,7 +515,7 @@ and expression_desc cxt ~(level : int) f x : cxt =
515515 (* TODO: dump for comments *)
516516 pp_function ?directive ~is_method ~return_unit ~async
517517 ~fn_state: default_fn_exp_state cxt f params body env
518- (* TODO:
518+ (* TODO:
519519 when [e] is [Js_raw_code] with arity
520520 print it in a more precise way
521521 It seems the optimizer already did work to make sure
@@ -524,6 +524,116 @@ and expression_desc cxt ~(level : int) f x : cxt =
524524 when Ext_list.length_equal el i
525525 ]}
526526 *)
527+ (* When -bs-preserve-jsx is enabled, we marked each transformed application node throughout the compilation.
528+ Here we print the transformed application node into a JSX syntax.
529+ The JSX is slightly different from what a user would write,
530+ but it is still valid JSX and is usable by tools like ESBuild.
531+ *)
532+ | Call
533+ ( ({
534+ expression_desc =
535+ J. Var
536+ (J. Qualified
537+ ( _,
538+ Some fnName
539+ (* We care about the function name when it is jsxs,
540+ If this is the case, we need to unpack an array later on *)
541+ ));
542+ } as e),
543+ el,
544+ {call_transformed_jsx = true } )
545+ when ! Js_config. jsx_preserve -> (
546+ (* We match a JsxRuntime.jsx call *)
547+ match el with
548+ | [
549+ tag;
550+ {
551+ expression_desc =
552+ (* This is the props javascript object *)
553+ Caml_block (el, _mutable_flag, _, Lambda. Blk_record {fields});
554+ };
555+ ] ->
556+ (* We extract the props from the javascript object *)
557+ let fields =
558+ Ext_list. array_list_filter_map fields el (fun (f , opt ) x ->
559+ match x.expression_desc with
560+ | Undefined _ when opt -> None
561+ | _ -> Some (f, x))
562+ in
563+ print_jsx cxt ~level f fnName tag fields
564+ | [
565+ tag;
566+ {
567+ expression_desc =
568+ Caml_block (el, _mutable_flag, _, Lambda. Blk_record {fields});
569+ };
570+ key;
571+ ] ->
572+ (* When a component has a key the matching runtime function call will have a third argument being the key *)
573+ let fields =
574+ Ext_list. array_list_filter_map fields el (fun (f , opt ) x ->
575+ match x.expression_desc with
576+ | Undefined _ when opt -> None
577+ | _ -> Some (f, x))
578+ in
579+ print_jsx cxt ~level ~key f fnName tag fields
580+ | [tag; ({expression_desc = J. Seq _} as props)] ->
581+ (* In the case of prop spreading, the expression will look like:
582+ (props.a = "Hello, world!", props)
583+ which is equivalent to
584+ <tag {...props} a="Hello, world!" />
585+
586+ We need to extract the props and the spread object.
587+ *)
588+ let fields, spread_props =
589+ let rec visit acc e =
590+ match e.J. expression_desc with
591+ | J. Seq
592+ ( {
593+ J. expression_desc =
594+ J. Bin
595+ ( Js_op. Eq ,
596+ {J. expression_desc = J. Static_index (_, name, _)},
597+ value );
598+ },
599+ rest ) ->
600+ visit ((name, value) :: acc) rest
601+ | _ -> (List. rev acc, e)
602+ in
603+ visit [] props
604+ in
605+ print_jsx cxt ~level ~spread_props f fnName tag fields
606+ | [tag; ({expression_desc = J. Seq _} as props); key] ->
607+ (* In the case of props + prop spreading and key argument *)
608+ let fields, spread_props =
609+ let rec visit acc e =
610+ match e.J. expression_desc with
611+ | J. Seq
612+ ( {
613+ J. expression_desc =
614+ J. Bin
615+ ( Js_op. Eq ,
616+ {J. expression_desc = J. Static_index (_, name, _)},
617+ value );
618+ },
619+ rest ) ->
620+ visit ((name, value) :: acc) rest
621+ | _ -> (List. rev acc, e)
622+ in
623+ visit [] props
624+ in
625+ print_jsx cxt ~level ~spread_props ~key f fnName tag fields
626+ | [tag; ({expression_desc = J. Var _} as spread_props)] ->
627+ (* All the props are spread *)
628+ print_jsx cxt ~level ~spread_props f fnName tag []
629+ | _ ->
630+ (* This should not happen, we fallback to the general case *)
631+ expression_desc cxt ~level f
632+ (Call
633+ ( e,
634+ el,
635+ {call_transformed_jsx = false ; arity = Full ; call_info = Call_ml }
636+ )))
527637 | Call (e , el , info ) ->
528638 P. cond_paren_group f (level > 15 ) (fun _ ->
529639 P. group f 0 (fun _ ->
@@ -960,6 +1070,103 @@ and expression_desc cxt ~(level : int) f x : cxt =
9601070 P. string f " ..." ;
9611071 expression ~level: 13 cxt f e)
9621072
1073+ and print_jsx cxt ?(spread_props : J.expression option )
1074+ ?(key : J.expression option ) ~(level : int ) f (fnName : string )
1075+ (tag : J.expression ) (fields : (string * J.expression) list ) : cxt =
1076+ let print_tag cxt =
1077+ match tag.expression_desc with
1078+ (* "div" or any other primitive tag *)
1079+ | J. Str {txt} ->
1080+ P. string f txt;
1081+ cxt
1082+ (* fragment *)
1083+ | J. Var (J. Qualified ({id = {name = "JsxRuntime" } } , Some "Fragment" )) -> cxt
1084+ (* A user defined component or external component *)
1085+ | _ -> expression ~level cxt f tag
1086+ in
1087+ let children_opt =
1088+ List. find_map
1089+ (fun (n , e ) ->
1090+ if n = " children" then
1091+ if fnName = " jsxs" then
1092+ match e.J. expression_desc with
1093+ | J. Array (xs, _)
1094+ | J. Optional_block ({expression_desc = J. Array (xs , _ )} , _ ) ->
1095+ Some xs
1096+ | _ -> Some [e]
1097+ else Some [e]
1098+ else None )
1099+ fields
1100+ in
1101+ let print_props cxt =
1102+ (* If a key is present, should be printed before the spread props,
1103+ This is to ensure tools like ESBuild use the automatic JSX runtime *)
1104+ let cxt =
1105+ match key with
1106+ | None -> cxt
1107+ | Some key ->
1108+ P. string f " key={" ;
1109+ let cxt = expression ~level: 0 cxt f key in
1110+ P. string f " } " ;
1111+ cxt
1112+ in
1113+ let props = List. filter (fun (n , _ ) -> n <> " children" ) fields in
1114+ let cxt =
1115+ match spread_props with
1116+ | None -> cxt
1117+ | Some spread ->
1118+ P. string f " {..." ;
1119+ let cxt = expression ~level: 0 cxt f spread in
1120+ P. string f " } " ;
1121+ cxt
1122+ in
1123+ if List. length props = 0 then cxt
1124+ else
1125+ (List. fold_left (fun acc (n , x ) ->
1126+ P. space f;
1127+ P. string f n;
1128+ P. string f " =" ;
1129+ P. string f " {" ;
1130+ let next = expression ~level: 0 acc f x in
1131+ P. string f " }" ;
1132+ next))
1133+ cxt props
1134+ in
1135+ match children_opt with
1136+ | None ->
1137+ P. string f " <" ;
1138+ let cxt = cxt |> print_tag |> print_props in
1139+ P. string f " />" ;
1140+ cxt
1141+ | Some children ->
1142+ let child_is_jsx child =
1143+ match child.J. expression_desc with
1144+ | J. Call (_ , _ , {call_transformed_jsx = is_jsx } ) -> is_jsx
1145+ | _ -> false
1146+ in
1147+
1148+ P. string f " <" ;
1149+ let cxt = cxt |> print_tag |> print_props in
1150+
1151+ P. string f " >" ;
1152+ if List. length children > 0 then P. newline f;
1153+
1154+ let cxt =
1155+ List. fold_left
1156+ (fun acc e ->
1157+ if not (child_is_jsx e) then P. string f " {" ;
1158+ let next = expression ~level acc f e in
1159+ if not (child_is_jsx e) then P. string f " }" ;
1160+ P. newline f;
1161+ next)
1162+ cxt children
1163+ in
1164+
1165+ P. string f " </" ;
1166+ let cxt = print_tag cxt in
1167+ P. string f " >" ;
1168+ cxt
1169+
9631170and property_name_and_value_list cxt f (l : J.property_map ) =
9641171 iter_lst cxt f l
9651172 (fun cxt f (pn , e ) ->
0 commit comments