Skip to content

Commit

Permalink
#1333 Add JS.Verbatim and JS.Html helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Jand42 committed May 12, 2023
1 parent 66d8c39 commit bb370a5
Show file tree
Hide file tree
Showing 19 changed files with 170 additions and 26 deletions.
2 changes: 1 addition & 1 deletion build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ open Fake.JavaScript
open WebSharper.Fake

let version = "7.0"
let pre = Some "beta1"
let pre = Some "beta2"

let baseVersion =
version + match pre with None -> "" | Some x -> "-" + x
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/WebSharper.Compiler.CSharp/Compile.fs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ let Compile config (logger: LoggerBase) tryGetMetadata =
if config.PrintJS then
match js with
| Some jss ->
for (name, js, _) in jss do
logger.Out("// " + name + ".js")
for (name, js, _, isJSX) in jss do
let x = if isJSX then "x" else ""
logger.Out("// " + name + ".js" + x)
logger.Out(js)
| _ -> ()

Expand All @@ -160,8 +161,9 @@ let Compile config (logger: LoggerBase) tryGetMetadata =
| Some path, Some jss ->
let asmPath = Path.Combine(path, thisName)
Directory.CreateDirectory(asmPath) |> ignore
for (name, js, _) in jss do
let jsPath = Path.Combine(asmPath, name + ".js")
for (name, js, _, isJSX) in jss do
let x = if isJSX then "x" else ""
let jsPath = Path.Combine(asmPath, name + ".js" + x)
File.WriteAllText(Path.Combine(Path.GetDirectoryName config.ProjectFile, jsPath), js)
logger.TimedStage ("Writing " + jsPath)
| _ -> ()
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/WebSharper.Compiler.FSharp/Compile.fs
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,9 @@ let Compile (config : WsConfig) (warnSettings: WarnSettings) (logger: LoggerBase
if config.PrintJS then
match js with
| Some jss ->
for (name, js, _) in jss do
logger.Out("// " + name + ".js")
for (name, js, _, isJSX) in jss do
let x = if isJSX then "x" else ""
logger.Out("// " + name + ".js" + x)
logger.Out(js)
| _ -> ()

Expand All @@ -267,8 +268,9 @@ let Compile (config : WsConfig) (warnSettings: WarnSettings) (logger: LoggerBase
let asmPath = Path.Combine(path, thisName)
Directory.CreateDirectory(asmPath) |> ignore
if resources |> Array.isEmpty || config.ProjectType <> None then
for (name, js, _) in jss do
let jsPath = Path.Combine(asmPath, name + ".js")
for (name, js, _, isJSX) in jss do
let x = if isJSX then "x" else ""
let jsPath = Path.Combine(asmPath, name + ".js" + x)
File.WriteAllText(Path.Combine(Path.GetDirectoryName config.ProjectFile, jsPath), js)
logger.TimedStage ("Writing " + jsPath)
else
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/WebSharper.Compiler/Breaker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,17 @@ let rec breakExpr expr : Broken<BreakResult> =
)
| ClassExpr(name, baseCls, mem) ->
ClassExpr(name, baseCls, mem |> List.map BreakStatement) |> broken
| Verbatim(stringParts, holes, isJSX) ->
let brHoles =
holes
|> List.map (fun e ->
let brE = breakExpr e
if hasNoStatements brE then
getExpr brE.Body
else
Application(Function([], None, None, brE |> toStatementsSpec Return |> List.ofSeq |> CombineStatements), [], ApplicationInfo.None)
)
Verbatim(stringParts, brHoles, isJSX) |> broken
| e ->
failwithf "Break expression error, not handled: %s" (Debug.PrintExpression e)

Expand Down
6 changes: 3 additions & 3 deletions src/compiler/WebSharper.Compiler/Bundle.fs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ module Bundling =

let scriptBase = o.Config.ScriptBaseUrl |> Option.defaultValue ""

let js, m =
let js, m, isJSX =
pkg
|> WebSharper.Compiler.JavaScriptPackager.addLoadedModules (List.ofSeq toLoad) scriptBase o.IsExtraBundle
|> WebSharper.Compiler.JavaScriptPackager.programToString O.JavaScript pref getCodeWriter
Expand Down Expand Up @@ -532,7 +532,7 @@ module Bundling =
let asm = refAssemblies |> List.find (fun asm -> asm.Name = b.AssemblyName)
let scripts = asm.GetScripts WebSharper.Core.JavaScript.Output.JavaScript
let findScript name =
name, (scripts |> Seq.find (fun s -> s.FileName = name)).Content
name, (scripts |> Seq.find (fun s -> s.FileName = name || s.FileName = name + "x")).Content
asm.Name, [findScript b.FileName; findScript b.MinifiedFileName]
)
|> Seq.append (
Expand All @@ -541,7 +541,7 @@ module Bundling =
let asm = refAssemblies |> List.find (fun asm -> asm.Name = a)
let scripts = asm.GetScripts WebSharper.Core.JavaScript.Output.JavaScript
let findScript name =
name, (scripts |> Seq.find (fun s -> s.FileName = name)).Content
name, (scripts |> Seq.find (fun s -> s.FileName = name || s.FileName = name + "x")).Content
jss
|> Seq.map (fun js ->
asm.Name, [findScript js]
Expand Down
1 change: 1 addition & 0 deletions src/compiler/WebSharper.Compiler/CompilationHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ let varEvalOrder (vars : Id list) expr =
| TraitCall _
| ObjectExpr _
| ClassExpr _
| Verbatim _
-> fail()

and evalSt s =
Expand Down
16 changes: 9 additions & 7 deletions src/compiler/WebSharper.Compiler/FrontEnd.fs
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,12 @@ let CreateResources (logger: LoggerBase) (comp: Compilation option) (refMeta: M.
let inline getBytes (x: string) = System.Text.Encoding.UTF8.GetBytes x
let jss =
pkg |> Array.map (fun (n, p) ->
let js, map = p |> WebSharper.Compiler.JavaScriptPackager.programToString O.JavaScript WebSharper.Core.JavaScript.Readable getCodeWriter
n, js, map
let js, map, isJSX = p |> WebSharper.Compiler.JavaScriptPackager.programToString O.JavaScript WebSharper.Core.JavaScript.Readable getCodeWriter
n, js, map, isJSX
)
for n, js, map in jss do
addRes (n + ".js") (Some (pu.JavaScriptFileName(ai))) (Some (getBytes js))
for n, js, map, isJSX in jss do
let x = if isJSX then "x" else ""
addRes (n + ".js" + x) (Some (pu.JavaScriptFileName(ai))) (Some (getBytes js))
map |> Option.iter (fun m ->
addRes (n + ".map") None (Some (getBytes m)))
logger.TimedStage (if sourceMap then "Writing .js and .map.js" else "Writing .js")
Expand All @@ -272,16 +273,17 @@ let CreateResources (logger: LoggerBase) (comp: Compilation option) (refMeta: M.
JavaScriptPackager.packageAssembly O.TypeScript refMeta current assemblyName (comp |> Option.bind (fun c -> c.EntryPoint)) JavaScriptPackager.EntryPointStyle.OnLoadIfExists
|> Array.map (fun (f, ts) -> f, ts |> List.map removeSourcePos.TransformStatement)
for (n, p) in tspkg do
let ts, _ = p |> WebSharper.Compiler.JavaScriptPackager.programToString O.TypeScript WebSharper.Core.JavaScript.Readable WebSharper.Core.JavaScript.Writer.CodeWriter
addRes (n + ".ts") (Some (pu.TypeScriptFileName(ai))) (Some (getBytes ts))
let ts, _, isJSX = p |> WebSharper.Compiler.JavaScriptPackager.programToString O.TypeScript WebSharper.Core.JavaScript.Readable WebSharper.Core.JavaScript.Writer.CodeWriter
let x = if isJSX then "x" else ""
addRes (n + ".ts" + x) (Some (pu.TypeScriptFileName(ai))) (Some (getBytes ts))
logger.TimedStage "Writing .ts files"

if dts then
let dtspkg =
JavaScriptPackager.packageAssembly O.TypeScriptDeclaration refMeta current assemblyName None JavaScriptPackager.EntryPointStyle.OnLoadIfExists
|> Array.map (fun (f, ts) -> f, ts |> List.map removeSourcePos.TransformStatement)
for (n, p) in dtspkg do
let ts, _ = p |> WebSharper.Compiler.JavaScriptPackager.programToString O.TypeScriptDeclaration WebSharper.Core.JavaScript.Readable WebSharper.Core.JavaScript.Writer.CodeWriter
let ts, _, _ = p |> WebSharper.Compiler.JavaScriptPackager.programToString O.TypeScriptDeclaration WebSharper.Core.JavaScript.Readable WebSharper.Core.JavaScript.Writer.CodeWriter
addRes (n + ".d.ts") (Some (pu.TypeScriptDeclarationFileName(ai))) (Some (getBytes ts))
logger.TimedStage "Writing .d.ts files"

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/WebSharper.Compiler/JavaScriptPackager.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ let addLoadedModules (urls: string list) scriptBase skipAssemblyDir (pkg: Statem
]

let programToString output pref (getWriter: unit -> WebSharper.Core.JavaScript.Writer.CodeWriter) statements =
let program = statements |> JavaScriptWriter.transformProgram output pref
let program, isJSX = statements |> JavaScriptWriter.transformProgram output pref
let writer = getWriter()
WebSharper.Core.JavaScript.Writer.WriteProgram pref writer program
writer.GetCodeFile(), writer.GetMapFile()
writer.GetCodeFile(), writer.GetMapFile(), isJSX
10 changes: 8 additions & 2 deletions src/compiler/WebSharper.Compiler/JavaScriptWriter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Environment =
mutable ScopeIds : Map<Id, string>
//ScopeVars : ResizeArray<string>
OuterScope : bool
IsJSX : bool ref
}
static member New(pref, ?mode) =
{
Expand All @@ -52,6 +53,7 @@ type Environment =
ScopeIds = Map [ Id.Global(), "globalThis"; Id.Import(), "import" ]
//ScopeVars = ResizeArray()
OuterScope = true
IsJSX = ref false
}

member this.NewInner() =
Expand All @@ -64,6 +66,7 @@ type Environment =
ScopeIds = this.ScopeIds
//ScopeVars = ResizeArray()
OuterScope = false
IsJSX = this.IsJSX
}

let undef = J.Unary(J.UnaryOperator.``void``, J.Constant (J.Literal.Number "0"))
Expand Down Expand Up @@ -373,6 +376,9 @@ let rec transformExpr (env: Environment) (expr: Expression) : J.Expression =
// (a.Address.Value |> List.rev |> String.concat ".") m
| GlobalAccessSet (a, v) ->
trE (GlobalAccess a) ^= trE v
| Verbatim (a, b, isJSX) ->
env.IsJSX.Value <- env.IsJSX.Value || isJSX
J.Verbatim(a, b |> List.map trE)
| _ ->
invalidForm (GetUnionCaseName expr)

Expand Down Expand Up @@ -724,11 +730,11 @@ and transformMember (env: Environment) (mem: Statement) : J.Member =
invalidForm (GetUnionCaseName mem)

let transformProgram output pref statements =
if List.isEmpty statements then [] else
if List.isEmpty statements then [], false else
let env = Environment.New(pref, output)
//let cnames = CollectStrongNames(env)
//statements |> List.iter cnames.VisitStatement
let cvars = CollectVariables(env)
statements |> List.iter cvars.VisitStatement
//J.Ignore (J.Constant (J.String "use strict")) ::
(statements |> List.map (transformStatement env) |> flattenJS)
(statements |> List.map (transformStatement env) |> flattenJS), env.IsJSX.Value
4 changes: 2 additions & 2 deletions src/compiler/WebSharper.Compiler/api/Assembly.fs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ module AssemblyUtility =
let ParseAllScriptResources (def: Mono.Cecil.AssemblyDefinition) (t: O) =
if IsWebSharperAssembly def then
match t with
| O.JavaScript -> ReadAllScriptResources def (fun n -> n.EndsWith(".js"))
| O.TypeScript -> ReadAllScriptResources def (fun n -> n.EndsWith(".ts") && not (n.EndsWith(".d.ts")))
| O.JavaScript -> ReadAllScriptResources def (fun n -> n.EndsWith(".js") || n.EndsWith(".jsx"))
| O.TypeScript -> ReadAllScriptResources def (fun n -> (n.EndsWith(".ts") && not (n.EndsWith(".d.ts")) || n.EndsWith(".tsx")))
| O.TypeScriptDeclaration -> ReadAllScriptResources def (fun n -> n.EndsWith(".d.ts"))
else Array.empty

Expand Down
1 change: 1 addition & 0 deletions src/compiler/WebSharper.Core.JavaScript/Syntax.fs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ and Expression =
| ExprComment of E * string
| ImportFunc
| ClassExpr of option<Id> * option<E> * list<E> * list<Member>
| Verbatim of list<string> * list<E>

static member ( + ) (a, b) = Binary (a, B.``+``, b)
static member ( - ) (a, b) = Binary (a, B.``-``, b)
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/WebSharper.Core.JavaScript/Writer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ let rec startsWithObjectExpression = function
| S.IgnoreEPos (S.Binary(e, _, _) | S.Application(e, _, _) | S.Conditional(e, _, _)) -> startsWithObjectExpression e
| _ -> false

module Seq =
let alternate (seq1: seq<_>) (seq2 : seq<_>) =
seq {
use e1 = seq1.GetEnumerator()
use e2 = seq2.GetEnumerator()
while e1.MoveNext() do
yield e1.Current
if e2.MoveNext() then
yield e2.Current
}

let rec Id (id: S.Id) =
Conditional (Token "...") id.Rest
++ Word (if id.IsTypeName then id.Name else (if id.IsPrivate then "#" else "") + EscapeId id.Name)
Expand Down Expand Up @@ -419,6 +430,13 @@ and Expression (expression) =
++ Optional (fun b -> Word "extends" ++ Expression b) b
++ OptionalList (fun i -> Word "implements" ++ CommaSeparated Expression i) i
++ BlockLayout (List.map (Member true) ms)
| S.Verbatim (s, e) ->
Token "(" ++ (
Seq.alternate
(s |> Seq.map Token)
(e |> Seq.map Expression)
|> Seq.reduce (++)
) ++ Token ")"

and Statement canBeEmpty statement =
match statement with
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/WebSharper.Core/AST.fs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ and Expression =
| ClassExpr of ClassId:option<Id> * BaseClass:option<Expression> * Members:list<Statement>
/// .NET - F# object expression
| ObjectExpr of ObjectType:Type * Constructor:option<Expression> * Overrides:list<TypeDefinition * Method * Expression>
/// JavaScript verbatim code
| Verbatim of StringParts:list<string> * Holes:list<Expression> * IsJSX:bool
with
static member (^!==) (a, b) = Binary (a, BinaryOperator.``!==``, b)
static member (^!=) (a, b) = Binary (a, BinaryOperator.``!=``, b)
Expand Down Expand Up @@ -505,6 +507,9 @@ type Transformer() =
/// .NET - F# object expression
abstract TransformObjectExpr : ObjectType:Type * Constructor:option<Expression> * Overrides:list<TypeDefinition * Method * Expression> -> Expression
override this.TransformObjectExpr (a, b, c) = ObjectExpr (a, Option.map this.TransformExpression b, List.map (fun (a, b, c) -> a, b, this.TransformExpression c) c)
/// JavaScript verbatim code
abstract TransformVerbatim : StringParts:list<string> * Holes:list<Expression> * IsJSX:bool -> Expression
override this.TransformVerbatim (a, b, c) = Verbatim (a, List.map this.TransformExpression b, c)
/// Empty statement
abstract TransformEmpty : unit -> Statement
override this.TransformEmpty () = Empty
Expand Down Expand Up @@ -672,6 +677,7 @@ type Transformer() =
| Cast (a, b) -> this.TransformCast (a, b)
| ClassExpr (a, b, c) -> this.TransformClassExpr (a, b, c)
| ObjectExpr (a, b, c) -> this.TransformObjectExpr (a, b, c)
| Verbatim (a, b, c) -> this.TransformVerbatim (a, b, c)
abstract TransformStatement : Statement -> Statement
override this.TransformStatement x =
match x with
Expand Down Expand Up @@ -879,6 +885,9 @@ type Visitor() =
/// .NET - F# object expression
abstract VisitObjectExpr : ObjectType:Type * Constructor:option<Expression> * Overrides:list<TypeDefinition * Method * Expression> -> unit
override this.VisitObjectExpr (a, b, c) = (); Option.iter this.VisitExpression b; List.iter (fun (a, b, c) -> this.VisitExpression c) c
/// JavaScript verbatim code
abstract VisitVerbatim : StringParts:list<string> * Holes:list<Expression> * IsJSX:bool -> unit
override this.VisitVerbatim (a, b, c) = (); List.iter this.VisitExpression b; ()
/// Empty statement
abstract VisitEmpty : unit -> unit
override this.VisitEmpty () = ()
Expand Down Expand Up @@ -1044,6 +1053,7 @@ type Visitor() =
| Cast (a, b) -> this.VisitCast (a, b)
| ClassExpr (a, b, c) -> this.VisitClassExpr (a, b, c)
| ObjectExpr (a, b, c) -> this.VisitObjectExpr (a, b, c)
| Verbatim (a, b, c) -> this.VisitVerbatim (a, b, c)
abstract VisitStatement : Statement -> unit
override this.VisitStatement x =
match x with
Expand Down Expand Up @@ -1145,6 +1155,7 @@ module IgnoreSourcePos =
let (|Cast|_|) x = match ignoreExprSourcePos x with Cast (a, b) -> Some (a, b) | _ -> None
let (|ClassExpr|_|) x = match ignoreExprSourcePos x with ClassExpr (a, b, c) -> Some (a, b, c) | _ -> None
let (|ObjectExpr|_|) x = match ignoreExprSourcePos x with ObjectExpr (a, b, c) -> Some (a, b, c) | _ -> None
let (|Verbatim|_|) x = match ignoreExprSourcePos x with Verbatim (a, b, c) -> Some (a, b, c) | _ -> None
let ignoreStatementSourcePos expr =
match expr with
| StatementSourcePos (_, e) -> e
Expand Down Expand Up @@ -1244,6 +1255,7 @@ module Debug =
| Cast (a, b) -> "Cast" + "(" + string a + ", " + PrintExpression b + ")"
| ClassExpr (a, b, c) -> "ClassExpr" + "(" + defaultArg (Option.map string a) "_" + ", " + defaultArg (Option.map PrintExpression b) "_" + ", " + "[" + String.concat "; " (List.map PrintStatement c) + "]" + ")"
| ObjectExpr (a, b, c) -> "ObjectExpr" + "(" + string a + ", " + defaultArg (Option.map PrintExpression b) "_" + ", " + "[" + String.concat "; " (List.map (fun (a, b, c) -> string a + ", " + string b + ", " + PrintExpression c) c) + "]" + ")"
| Verbatim (a, b, c) -> "Verbatim" + "(" + "[" + String.concat "; " (List.map string a) + "]" + ", " + "[" + String.concat "; " (List.map PrintExpression b) + "]" + ", " + string c + ")"
and PrintStatement x =
match x with
| Empty -> "Empty" + ""
Expand Down
48 changes: 48 additions & 0 deletions src/compiler/WebSharper.Core/Macros.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,54 @@ type PrintF() =
createPrinter c.Compilation ts (Some args) fs |> MacroOk
| _ -> MacroError "printfMacro error"

[<AbstractClass>]
type StringInterpolationBase() =
inherit Macro()

abstract Process: stringParts: list<string> * holes: list<Expression> -> MacroResult

override this.TranslateCall(c) =
match c.Arguments with
| [I.Value (Literal.String fs)] ->
this.Process([fs], [])
| [I.Call (None, etlo, pfts, [ I.Ctor (pf, _, pfArgs) ])] when
etlo.Entity.Value.FullName = "Microsoft.FSharp.Core.ExtraTopLevelOperators"
&& pfts.Entity.Value.MethodName = "PrintFormatToString"
&& pf.Entity.Value.FullName = "Microsoft.FSharp.Core.PrintfFormat`5" ->
match pfArgs with
| [ I.Value (Literal.String fs) ] ->
this.Process([fs], [])
| [I.Value (Literal.String fs); I.NewArray args; _] ->
this.Process(fs.Split([| "%P()" |], System.StringSplitOptions.None) |> List.ofArray, args)
| _ ->
MacroError $"{this.GetType().Name} macro expects a string literal or string interpolation argument"
| _ -> MacroError $"{this.GetType().Name} macro expects a string literal or string interpolation argument"

[<Sealed>]
type JSVerbatim() =
inherit StringInterpolationBase()

override this.Process(stringParts, holes) =
Verbatim(stringParts, holes, false) |> MacroOk

[<Sealed>]
type JSHtml() =
inherit StringInterpolationBase()

override this.Process(stringParts, holes) =
let l = stringParts.Length - 1
let addedBraces =
stringParts
|> List.mapi (fun i s ->
if i = 0 then
if l > 0 then s + "{" else s
elif i = l then
"}" + s
else
"}" + s + "{"
)
Verbatim(addedBraces, holes, true) |> MacroOk

let rec isImplementing (comp: M.ICompilation) typ intf =
comp.GetClassInfo typ
|> Option.map (fun cls ->
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/WebSharper.Core/Macros.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@ type PrintF =
new : unit -> PrintF
inherit Macro

[<AbstractClass>]
type StringInterpolationBase =
new : unit -> StringInterpolationBase
inherit Macro

abstract Process: stringParts: list<string> * holes: list<WebSharper.Core.AST.Expression> -> MacroResult

[<Sealed>]
type JSVerbatim =
new : unit -> JSVerbatim
inherit StringInterpolationBase

[<Sealed>]
type JSHtml =
new : unit -> JSHtml
inherit StringInterpolationBase

[<Sealed>]
type Comp =
inherit Macro
Expand Down
Loading

0 comments on commit bb370a5

Please sign in to comment.