@@ -515,7 +515,7 @@ and expression_desc cxt ~(level : int) f x : cxt =
515
515
(* TODO: dump for comments *)
516
516
pp_function ?directive ~is_method ~return_unit ~async
517
517
~fn_state: default_fn_exp_state cxt f params body env
518
- (* TODO:
518
+ (* TODO:
519
519
when [e] is [Js_raw_code] with arity
520
520
print it in a more precise way
521
521
It seems the optimizer already did work to make sure
@@ -524,6 +524,116 @@ and expression_desc cxt ~(level : int) f x : cxt =
524
524
when Ext_list.length_equal el i
525
525
]}
526
526
*)
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
+ )))
527
637
| Call (e , el , info ) ->
528
638
P. cond_paren_group f (level > 15 ) (fun _ ->
529
639
P. group f 0 (fun _ ->
@@ -960,6 +1070,103 @@ and expression_desc cxt ~(level : int) f x : cxt =
960
1070
P. string f " ..." ;
961
1071
expression ~level: 13 cxt f e)
962
1072
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
+
963
1170
and property_name_and_value_list cxt f (l : J.property_map ) =
964
1171
iter_lst cxt f l
965
1172
(fun cxt f (pn , e ) ->
0 commit comments