From 1c761c22d7da30c149b9554db5fa1749c9d33904 Mon Sep 17 00:00:00 2001 From: Alfonso Garcia-Caro Date: Thu, 26 May 2022 07:21:25 +0900 Subject: [PATCH] Enable JSX (interpolates) string templates --- src/Fable.Core/Fable.Core.JS.fs | 1 + src/Fable.Transforms/BabelPrinter.fs | 38 ++++++++++++++++++++--- src/Fable.Transforms/Fable2Babel.fs | 9 ++++++ src/Fable.Transforms/Global/Babel.fs | 3 ++ src/Fable.Transforms/Replacements.Util.fs | 2 +- src/Fable.Transforms/Replacements.fs | 2 ++ 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/Fable.Core/Fable.Core.JS.fs b/src/Fable.Core/Fable.Core.JS.fs index 0e8ecdaad0..d058e10b92 100644 --- a/src/Fable.Core/Fable.Core.JS.fs +++ b/src/Fable.Core/Fable.Core.JS.fs @@ -16,6 +16,7 @@ module JSX = class end let create (componentOrTag: obj) (props: Prop list): Element = nativeOnly + let html (template: string): Element = nativeOnly module JS = type [] Function = diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index bc0269f5bf..840d19c4ce 100644 --- a/src/Fable.Transforms/BabelPrinter.fs +++ b/src/Fable.Transforms/BabelPrinter.fs @@ -256,6 +256,7 @@ module PrinterExtensions = | SpreadElement(_) | ArrayExpression(_) | ObjectExpression(_) + | JsxTemplate(_) | JsxElement(_) -> printer.Print(expr) | _ -> printer.WithParens(expr) @@ -292,7 +293,26 @@ module PrinterExtensions = | Node.ObjectTypeCallProperty(_) | Node.ObjectTypeInternalSlot(_) -> failwith "Not implemented" - member printer.PrintJsx(componentOrTag: Expression, props: (string * Expression) list, children: Expression list) = + member printer.PrintJsxTemplate(parts: string[], values: Expression[]) = + // Do we need to escape backslashes here? + let escape str = str //Regex.Replace(str, @"(? + printer.PrintJsxElement(componentOrTag, props, children) + | JsxTemplate(parts, values) -> + printer.PrintJsxTemplate(parts, values) + | value -> + printer.Print("{") + printer.Print(value) + printer.Print("}") + printer.Print(Array.last parts |> escape) + + member printer.PrintJsxElement(componentOrTag: Expression, props: (string * Expression) list, children: Expression list) = let printTag = function | StringConstant tag -> printer.Print(tag) | componentRef -> printer.Print(componentRef) @@ -336,7 +356,10 @@ module PrinterExtensions = printer.Print(text) printer.PrintNewLine() | JsxElement(componentOrTag, props, children) -> - printer.PrintJsx(componentOrTag, props, children) + printer.PrintJsxElement(componentOrTag, props, children) + printer.PrintNewLine() + | JsxTemplate(parts, values) -> + printer.PrintJsxTemplate(parts, values) printer.PrintNewLine() | child -> printer.Print("{") @@ -352,7 +375,8 @@ module PrinterExtensions = member printer.Print(expr: Expression) = match expr with - | JsxElement(componentOrTag, props, children) -> printer.PrintJsx(componentOrTag, props, children) + | JsxElement(componentOrTag, props, children) -> printer.PrintJsxElement(componentOrTag, props, children) + | JsxTemplate(parts, values) -> printer.PrintJsxTemplate(parts, values) | Super(loc) -> printer.Print("super", ?loc = loc) | Literal(n) -> printer.Print(n) | Undefined(loc) -> printer.Print("undefined", ?loc=loc) @@ -442,7 +466,13 @@ module PrinterExtensions = | Statement.BlockStatement(s) -> printer.Print(s) | ReturnStatement(argument, loc) -> printer.Print("return ", ?loc = loc) - printer.Print(argument) + // If a JSX template starts with a new line, surround it in parens to avoid + // having only return in single line (this causes JS to ignore the rest of the code) + match argument with + | JsxTemplate(parts,_) when Regex.IsMatch(parts[0], @"^\s*\n") -> + printer.WithParens(argument) + | _ -> + printer.Print(argument) | SwitchStatement(discriminant, cases, loc) -> printer.PrintSwitchStatement(discriminant, cases, loc) | LabeledStatement(body, label) -> printer.PrintLabeledStatement(body, label) | DebuggerStatement(loc) -> printer.Print("debugger", ?loc = loc) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index d3ea2a77c5..435f537c4f 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -1294,6 +1294,15 @@ module Util = | Some "jsx", _ -> "Expecting a static list or array literal (no generator) for JSX props" |> addErrorAndReturnNull com range |> Some + | Some "jsx-template", args -> + match args with + | StringConst template ::_ -> Expression.jsxTemplate(template) |> Some + | MaybeCasted(Fable.Value(Fable.StringTemplate(_, parts, values), _))::_ -> + let values = values |> List.mapToArray (transformAsExpr com ctx) + Expression.jsxTemplate(List.toArray parts, values) |> Some + | _ -> + $"Expecting an interpolated string literal without formatting, found %A{args}" + |> addErrorAndReturnNull com range |> Some | _ -> None match optimized, callInfo.CallMemberInfo with diff --git a/src/Fable.Transforms/Global/Babel.fs b/src/Fable.Transforms/Global/Babel.fs index d0ccdd6476..ffa3b801e7 100644 --- a/src/Fable.Transforms/Global/Babel.fs +++ b/src/Fable.Transforms/Global/Babel.fs @@ -57,6 +57,7 @@ type Node = /// Since the left-hand side of an assignment may be any expression in general, an expression can also be a pattern. type Expression = | JsxElement of componentOrTag: Expression * props: (string * Expression) list * children: Expression list + | JsxTemplate of parts: string[] * values: Expression[] | Literal of Literal | Identifier of Identifier | ClassExpression of @@ -513,6 +514,8 @@ type InterfaceExtends = module Helpers = type Expression with static member jsxElement(componentOrTag, props, children) = JsxElement(componentOrTag, props, children) + static member jsxTemplate(parts, values) = JsxTemplate(parts, values) + static member jsxTemplate(part) = JsxTemplate([|part|], [||]) static member super(?loc) = Super loc static member emitExpression(value, args, ?loc) = EmitExpression(value, args, loc) static member nullLiteral(?loc) = NullLiteral loc |> Literal diff --git a/src/Fable.Transforms/Replacements.Util.fs b/src/Fable.Transforms/Replacements.Util.fs index 823635f1d6..18b8782da7 100644 --- a/src/Fable.Transforms/Replacements.Util.fs +++ b/src/Fable.Transforms/Replacements.Util.fs @@ -440,7 +440,7 @@ let tryFindInScope (ctx: Context) identName = let (|MaybeInScope|) (ctx: Context) e = match e with - | IdentExpr ident -> + | MaybeCasted(IdentExpr ident) -> match tryFindInScope ctx ident.Name with | Some e -> e | None -> e diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 7006699f97..a0b8ffad93 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -968,6 +968,8 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp | _ -> None | "Fable.Core.JSX", "create" -> Helper.LibCall(com, "JSX", "create", t, args, ?loc=r) |> withTag "jsx" |> Some + | "Fable.Core.JSX", "html" -> + Helper.LibCall(com, "JSX", "html", t, args, ?loc=r) |> withTag "jsx-template" |> Some | _ -> None let getReference r t expr = getFieldWith r t expr "contents"