Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Suave in watch mode (during development)

Tags:

f#

suave

I am working on Suave 1.0 + Angular 2.0 sample app and very interesting to start Suave server in watch mode, so the server watch file changes (js,css,html) in root folder and sub-folders and automatically send refresh command to all open browser tabs with my application when any file is changed.

lite-server from Angular 2 5min Quckstark can do this and it is very handy.

I think that most of watch pieces can be found in latest Steffen Forkmann's post but it is not very clean how to send refresh to the open browser tabs.

Please provide complete code of similar implementation with Suave.

like image 784
Sergey Tihon Avatar asked Jan 05 '16 03:01

Sergey Tihon


Video Answer


1 Answers

The code of Suave server should looks similar to this

#r "packages/Suave/lib/net40/suave.dll"
#r "packages/FAKE/tools/FakeLib.dll"

open Fake
open Suave
open Suave.Operators
open Suave.Sockets.Control
open Suave.WebSocket
open Suave.Utils
open Suave.Files
open Suave.RequestErrors
open Suave.Filters
open System
open System.Net


let port =
    let rec findPort port =
        let portIsTaken =
            System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
            |> Seq.exists (fun x -> x.Port = int(port))
        if portIsTaken then findPort (port + 1us) else port
    findPort 8083us

let logger = Logging.Loggers.ConsoleWindowLogger Logging.LogLevel.Verbose

let refreshEvent = new Event<_>()

let handleWatcherEvents (events:FileChange seq) =
    for e in events do
        let fi = fileInfo e.FullPath
        traceImportant <| sprintf "%s was changed." fi.Name
    refreshEvent.Trigger()

let socketHandler (webSocket : WebSocket) =
  fun cx -> socket {
    while true do
      let! refreshed =
        Control.Async.AwaitEvent(refreshEvent.Publish)
        |> Suave.Sockets.SocketOp.ofAsync
      do! webSocket.send Text (ASCII.bytes "refreshed") true
  }

let cfg =
  { defaultConfig with
      homeFolder = Some (__SOURCE_DIRECTORY__)
      bindings =
        [ HttpBinding.mk HTTP IPAddress.Loopback port ]
      listenTimeout = TimeSpan.FromMilliseconds 3000. }

let app : WebPart =
  choose [
    Filters.log logger logFormat >=> never
    Filters.path "/websocket" >=> handShake socketHandler
    Filters.GET >=> Filters.path "/" >=> file "index.html"
    Writers.setHeader "Cache-Control" "no-cache, no-store, must-revalidate"
      >=> Writers.setHeader "Pragma" "no-cache"
      >=> Writers.setHeader "Expires" "0"
      >=> browseHome
    NOT_FOUND "Found no handlers."
  ]


let watcher =
    !! ("app/*.js")
      ++ ("*.html")
    |> WatchChanges handleWatcherEvents

try
    System.Diagnostics.Process.Start(sprintf "http://localhost:%d/index.html" port) |> ignore
    startWebServer cfg app
finally
    watcher.Dispose()

So we setup watcher that handle changes in js(generated by TypeScript) and html files and send refresh command to the client, but in the same time we need to add following code to the head section of index.html to handle refresh on the client side

<!-- 3. Listen on refresh events from the server -->
<script language="javascript" type="text/javascript">
    function init()
    {
        websocket = new WebSocket("ws://"+window.location.host+"/websocket");
        websocket.onmessage = function(evt) { location.reload(); };
    }
    window.addEventListener("load", init, false);
</script>

The full demo app you can find here

like image 193
Sergey Tihon Avatar answered Sep 18 '22 19:09

Sergey Tihon