By Adam Granicz on Monday, June 15, 2015 — 8 comments

Introducing WebSharper WarpCore team

WebSharper Warp is a friction-less web development library for building scripted and standalone full-stack F# client-server applications. Warp is built on top of WebSharper and is designed to help you become more productive and benefit from the rich WebSharper features more quickly and more directly. While Warp shorthands target the most typical applications (text, SPAs, multi-page) and easy exploration, you can extend your Warp applications with the full WebSharper capabilities at any time.

Installing

To get started with Warp is super-easy, all you need is to open a new F# Console Application (or any other F# project type if you want to script applications), and add WebSharper.Warp to it:

1
Install-Package WebSharper.Warp

Or if you use Paket:

1
2
paket init
paket add nuget WebSharper.Warp

Hello world!

The simplest Warp site just serves text and consist of a single endpoint (/), by default listening on http://localhost:9000.

1
2
3
4
5
6
open WebSharper

let MyApp = Warp.Text "Hello world!"

[<EntryPoint>]
do Warp.RunAndWaitForInput(MyApp) |> ignore

Single Page Applications

While serving text is fun and often useful, going beyond isn't any complicated. Warp also helps constructing HTML. In the most basic form, you can create single page applications (SPAs) using Warp.CreateSPA and WebSharper's server-side HTML combinators:

1
2
3
4
5
6
7
8
open WebSharper.Html.Server

let MySite =
    Warp.CreateSPA (fun ctx ->
        [H1 [Text "Hello world!"]])

[<EntryPoint>]
do Warp.RunAndWaitForInput(MySite) |> ignore

Multi-page applications

Using multiple EndPoints and Warp.CreateApplication, you can define multi-page Warp applications. When constructing the actual pages, Warp.Page comes handy - allowing you to fill the Title, Head, and the Body parts on demand. Warp.Page pages are fully autonomous and will automatically contain the dependencies of any client-side code used on the page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Endpoints =
    | [<EndPoint "GET /">] Home
    | [<EndPoint "GET /about">] About

let MySite =
    Warp.CreateApplication (fun ctx endpoint ->
        let (=>) label endpoint = A [HRef (ctx.Link endpoint)] -< [Text label]
        match endpoint with
        | Endpoints.Home ->
            Warp.Page(
                Body =
                    [
                        H1 [Text "Hello world!"]
                        "About" => Endpoints.About
                    ]
            )
        | Endpoints.About ->
            Warp.Page(
                Body =
                    [
                        P [Text "This is a simple app"]
                        "Home" => Endpoints.Home
                    ]
            )
    )

[<EntryPoint>]
do Warp.RunAndWaitForInput(MySite) |> ignore

Adding client-side functionality

Warp applications can easily incorporate client-side content and functionality, giving an absolute edge over any web development library. The example below is reimplemented from Deploying WebSharper apps to Azure via GitHub, and although it omits the more advanced templating in that approach (which is straightforward to add to this implementation), it greatly simplifies constructing and running the application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module Server =
    [<Server>]
    let DoWork (s: string) = 
        async {
            return System.String(List.ofSeq s |> List.rev |> Array.ofList)
        }

[<Client>]
module Client =
    open WebSharper.JavaScript
    open WebSharper.Html.Client

    let Main () =
        let input = Input [Attr.Value ""]
        let output = H1 []
        Div [
            input
            Button([Text "Send"])
                .OnClick (fun _ _ ->
                    async {
                        let! data = Server.DoWork input.Value
                        output.Text <- data
                    }
                    |> Async.Start
                )
            HR []
            H4 [Class "text-muted"] -- Text "The server responded:"
            Div [Class "jumbotron"] -< [output]
        ]

let MySite =
    Warp.CreateSPA (fun ctx ->
        [
            H1 [Text "Say Hi to the server"]
            Div [ClientSide <@ Client.Main() @>]
        ])

[<EntryPoint>]
do Warp.RunAndWaitForInput(MySite) |> ignore

Taking things further

Creating RESTful applications, using client-side visualizations is just as easy. For a quick example, here is a Chart.js-based visualization using the WebSharper.ChartJs WebSharper extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[<Client>]
module Client =
    open WebSharper.JavaScript
    open WebSharper.Html.Client
    open WebSharper.ChartJs

    let RadarChart () =
        Div [
            H3 [Text "Activity Chart"]
            Canvas [Attr.Width  "450"; Attr.Height "300"]
            |>! OnAfterRender (fun canvas ->
                let canvas = As<CanvasElement> canvas.Dom
                RadarChartData(
                    Labels   = [| "Eating"; "Drinking"; "Sleeping";
                                  "Designing"; "Coding"; "Cycling"; "Running" |],
                    Datasets = [|
                        RadarChartDataset(
                            FillColor   = "rgba(151, 187, 205, 0.2)",
                            StrokeColor = "rgba(151, 187, 205, 1)",
                            PointColor  = "rgba(151, 187, 205, 1)",
                            Data        = [|28.0; 48.0; 40.0; 19.0; 96.0; 27.0; 100.0|]
                        )
                        RadarChartDataset(
                            FillColor   = "rgba(220, 220, 220, 0.2)",
                            StrokeColor = "rgba(220, 220, 220, 1)",
                            PointColor  = "rgba(220,220,220,1)",
                            Data        = [|65.0; 59.0; 90.0; 81.0; 56.0; 55.0; 40.0|]
                        )
                    |]
                )
                |> Chart(canvas.GetContext "2d").Radar
                |> ignore
            )
        ]

let MySite =
    Warp.CreateSPA (fun ctx ->
        [
            H1 [Text "Charts are easy with WebSharper Warp!"]
            Div [ClientSide <@ Client.RadarChart() @>]
        ])

[<EntryPoint>]
do Warp.RunAndWaitForInput(MySite) |> ignore

Scripting with Warp

When you add the WebSharper.Warp NuGet package to your project in Visual Studio, a new document tab will open giving the necessary boilerplate for using Warp in scripted applications.

For instance, the SPA example above can be written as an F# script and executed in F# Interative:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#I "../packages/Owin.1.0/lib/net40"
#I "../packages/Microsoft.Owin.3.0.1/lib/net45"
#I "../packages/Microsoft.Owin.Host.HttpListener.3.0.1/lib/net45"
#I "../packages/Microsoft.Owin.Hosting.3.0.1/lib/net45"
#I "../packages/Microsoft.Owin.FileSystems.3.0.1/lib/net45"
#I "../packages/Microsoft.Owin.StaticFiles.3.0.1/lib/net45"
#I "../packages/WebSharper.3.2.8.170/lib/net40"
#I "../packages/WebSharper.Compiler.3.2.4.170/lib/net40"
#I "../packages/WebSharper.Owin.3.2.6.83/lib/net45"
#load "../packages/WebSharper.Warp.3.2.10.13/tools/reference.fsx"

open WebSharper
open WebSharper.Html.Server

let MySite =
    Warp.CreateSPA (fun ctx ->
        [H1 [Text "Hello world!"]])

do Warp.RunAndWaitForInput(MySite) |> ignore

If you use Paket, then you should replace the #-lines above with this one:

1
#load "../packages/WebSharper.Warp/tools/reference-nover.fsx"

In FSI, you should see:

1
2
3
4
5
6
7
8
--> Added 'c:\sandbox\test\Library1\HelloWorld\../packages/Owin.1.0/lib/net40' to library include path
[... more lines ...]

[Loading c:\sandbox\test\Library1\packages\WebSharper.Warp.3.2.10.13\tools\reference.fsx]

namespace FSI_0004

Serving http://localhost:9000/, press Enter to stop.

You can then test this application as before:

Getting help

Warp now has a chat room where you can ask questions, feel free to drop by:

Gitter

Happy coding!

  • arcsech@gmail.com

    From looking at the source (which is really quite short, less than 250 lines including whitespace and comments), this looks like a thin wrapper around sitelets to make theem easier to use. Which is great, since Warp-based applications can integrate with things you need plain sitelets for (like REST APIs) really easily.

    The only thing that's not so great is the name - Warp is already used by a major Haskell web server library: (http://www.stackage.org/package/warp)

    • adam.granicz

      That's a bit unfortunate, indeed. I hope both libraries can win people over from the non-FP camps and benefit from that shared name.

      • Jacqueline Homan

        Adam, ya'll already won me over from the OOP camp with F# before seeing this. When I saw the language, it was when a sr developer who showed me how to write a parser to slice through a clobbered YAML file that Ruby's Nokogiri gem choked on.

        I didn't come from a computer science or web designer background, either. But I do have a 4 year in pure (i.e. not applied) math, so I think (and code) like a mathematician. I know that there is no programming language that is the "magic bullet" of magic bullets, but if given a choice, I would use F# for most applications that entail data association modeling.

        In fact, since being turned on to F# a few months ago, and being VERY impressed with it before even seeing this WebSharper tool, I made a deliberate decision to use F# for a data-rich project I am bootstrapping.

        On final word: I am a self-taught programmer that comes from a very disadvantaged background. I am teaching myself F#. Are you familiar with the saying, "If you build it, they will come?" Well..because someone built F# and behold, I came :)

        I eventually would like to learn Haskell and R too. I mean, Ruby on Rails was sorta neat in a bells and whistles kind of way. That's where I started learning programming as an older woman 2 years ago. PHP is its own kind of frustrating hell. But F# hit my coding "g-spot", if you get me. (Sorry for the TMI)

        let jacquelineFavLang() = printfn "F#"

  • Jacqueline Homan

    This is AWESOME! I am playing around with this now, going to graph some cool mathematical functions :)

  • Jacqueline Homan

    You know what, I just modified Adam's excellent demo by adding a Contact Us page, and also adding a paragraph of text explaining to Rubyists that they should think of the DU code block, "type EndPoints", as the F# equivalent of Rails' routes.

  • adam.granicz

    For those using Paket, I just carried over a couple bits from Loic's commit to the Warp README about how to install Warp with Paket into your projects, and how you can just include a single file when running scripted apps via Paket.

  • Paweł Stadnicki

    I wanted to play with Warp, copy & paste the client-side sample but compiler is not happy with these lines:

    1
    2
    3
    4
    5
    
    Warp.CreateSPA (fun ctx ->
    [
    H1 [Text "Say Hi to the server"]
    Div [ClientSide <@ Client.Main() @>]
    ])

    There are 4 errors, I suspect that one correction resolve all of them at once, appreciate suggestions what should be exactly corrected

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     1. The type 'Web.InlineControl<'a>' is not compatible with the type 'Pagelet'	ConsoleApplication1	
     Severity	Code	Description	Project	File	Line
    Error	No overloads match for method 'CreateSPA'. The available overloads are shown below (or in the Error List window).	ConsoleApplication1	...
     
     2. Possible overload: 'static member Warp.CreateSPA : f:(Sitelets.Context<SPA.Endpoints> -> Sitelets.Content<SPA.Endpoints>) -> WarpApplication<SPA.Endpoints>'. Type constraint mismatch. The type 
    Sitelets.Context<SPA.Endpoints> -> Element list 
    is not compatible with type
    Sitelets.Context<SPA.Endpoints> -> Sitelets.Content<SPA.Endpoints> 
    The type 'Sitelets.Content<SPA.Endpoints>' does not match the type 'Element list'.	ConsoleApplication1	
    
     3. Possible overload: 'static member Warp.CreateSPA : f:(Sitelets.Context<SPA.Endpoints> -> #seq<Server.Html.Element>) -> WarpApplication<SPA.Endpoints>'. Type constraint mismatch. The type 
    Sitelets.Context<SPA.Endpoints> -> Element list 
    is not compatible with type
    Sitelets.Context<SPA.Endpoints> -> 'a 
    The type 'Element list' is not compatible with the type 'seq<Server.Html.Element>'.	ConsoleApplication1
    • adam.granicz

      Sounds like you are trying to use the client-side HTML language (from WebSharper.Html.Client) instead of the server-side one in your CreateSPA function. The snippets in the blog entry are "incremental", so the full code looks like this:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      
      open WebSharper
      
      module Server =
          [<Server>]
          let DoWork (s: string) = 
              async {
                  return System.String(List.ofSeq s |> List.rev |> Array.ofList)
              }
      
      [<Client>]
      module Client =
          open WebSharper.JavaScript
          open WebSharper.Html.Client
      
          let Main () =
              let input = Input [Attr.Value ""]
              let output = H1 []
              Div [
                  input
                  Button([Text "Send"])
                      .OnClick (fun _ _ ->
                          async {
                              let! data = Server.DoWork input.Value
                              output.Text <- data
                          }
                          |> Async.Start
                      )
                  HR []
                  H4 [Class "text-muted"] -- Text "The server responded:"
                  Div [Class "jumbotron"] -< [output]
              ]
      
      open WebSharper.Html.Server
      
      let MySite =
          Warp.CreateSPA (fun ctx ->
              [
                  H1 [Text "Say Hi to the server"]
                  Div [ClientSide <@ Client.Main() @>]
              ])
      
      [<EntryPoint>]
      do Warp.RunAndWaitForInput(MySite) |> ignore

      Note the use of the right HTML language in the right places.