11namespace fsdocs
22
3+ open System.Collections .Concurrent
34open CommandLine
45
56open System
@@ -25,6 +26,7 @@ open Suave.Sockets.Control
2526open Suave.WebSocket
2627open Suave.Operators
2728open Suave.Filters
29+ open Suave.Logging
2830open FSharp.Formatting .Markdown
2931
3032#nowarn " 44" // Obsolete WebClient
@@ -627,8 +629,7 @@ type internal DocContent
627629
628630/// Processes and runs Suave server to host them on localhost
629631module Serve =
630- //not sure what this was needed for
631- //let refreshEvent = new Event<_>()
632+ let refreshEvent = FSharp.Control.Event< string>()
632633
633634 /// generate the script to inject into html to enable hot reload during development
634635 let generateWatchScript ( port : int ) =
@@ -639,33 +640,67 @@ module Serve =
639640 function init()
640641 {
641642 websocket = new WebSocket(wsUri);
642- websocket.onclose = function(evt) { onClose(evt) };
643- }
644- function onClose(evt)
645- {
646- console.log('closing');
647- websocket.close();
648- document.location.reload();
643+ websocket.onmessage = function(evt) {
644+ const data = evt.data;
645+ if (data.endsWith(".css")) {
646+ console.log(`Trying to reload ${data}`);
647+ const link = document.querySelector(`link[href*='${data}']`);
648+ if (link) {
649+ const href = new URL(link.href);
650+ const ticks = new Date().getTime();
651+ href.searchParams.set("v", ticks);
652+ link.href = href.toString();
653+ }
654+ }
655+ else {
656+ console.log('closing');
657+ websocket.close();
658+ document.location.reload();
659+ }
660+ }
649661 }
650662 window.addEventListener("load", init, false);
651663</script>
652664"""
653665
654666 tag.Replace( " {{PORT}}" , string port)
655667
668+ let connectedClients = ConcurrentDictionary< WebSocket, unit>()
656669
670+ let socketHandler ( webSocket : WebSocket ) ( context : HttpContext ) =
671+ context.runtime.logger.info ( Message.eventX " New websocket connection" )
672+ connectedClients.TryAdd( webSocket, ()) |> ignore
657673
658- let signalHotReload = new System.Threading.ManualResetEvent( false )
659-
660- let socketHandler ( webSocket : WebSocket ) _ =
661674 socket {
662- signalHotReload.WaitOne() |> ignore
663- signalHotReload.Reset() |> ignore
664- let emptyResponse = [||] |> ByteSegment
665- printfn " Triggering hot reload on the client"
666- do ! webSocket.send Close emptyResponse true
675+ let! msg = webSocket.read ()
676+
677+ match msg with
678+ | Close, _, _ ->
679+ context.runtime.logger.info ( Message.eventX " Closing connection" )
680+ connectedClients.TryRemove webSocket |> ignore
681+ let emptyResponse = [||] |> ByteSegment
682+ do ! webSocket.send Close emptyResponse true
683+ | _ -> ()
667684 }
668685
686+ let broadCastReload ( msg : string ) =
687+ let msg = msg |> Encoding.UTF8.GetBytes |> ByteSegment
688+
689+ connectedClients.Keys
690+ |> Seq.map ( fun client ->
691+ async {
692+ let! _ = client.send Text msg true
693+ ()
694+ })
695+ |> Async.Parallel
696+ |> Async.Ignore
697+ |> Async.RunSynchronously
698+
699+ refreshEvent.Publish
700+ |> Event.add ( fun fileName ->
701+ let fileName = fileName.TrimEnd( '~' )
702+ broadCastReload fileName)
703+
669704 let startWebServer rootOutputFolderAsGiven localPort =
670705 let mimeTypesMap ext =
671706 match ext with
@@ -1743,9 +1778,9 @@ type CoreBuildOptions(watch) =
17431778 let mutable docsQueued = true
17441779 let mutable generateQueued = true
17451780
1746- let docsDependenciesChanged = Event<_ >()
1781+ let docsDependenciesChanged = FSharp.Control. Event< string >()
17471782
1748- docsDependenciesChanged.Publish.Add( fun () ->
1783+ docsDependenciesChanged.Publish.Add( fun fileName ->
17491784 if not docsQueued then
17501785 docsQueued <- true
17511786 printfn " Detected change in '%s ', scheduling rebuild of docs..." this.input
@@ -1760,11 +1795,11 @@ type CoreBuildOptions(watch) =
17601795 if runDocContentPhase2 () then
17611796 regenerateSearchIndex ())
17621797
1763- Serve.signalHotReload.Set () |> ignore
1798+ Serve.refreshEvent.Trigger fileName
17641799 }
17651800 |> Async.Start)
17661801
1767- let apiDocsDependenciesChanged = Event<_>()
1802+ let apiDocsDependenciesChanged = FSharp.Control. Event<_>()
17681803
17691804 apiDocsDependenciesChanged.Publish.Add( fun () ->
17701805 if not generateQueued then
@@ -1781,15 +1816,15 @@ type CoreBuildOptions(watch) =
17811816 if runGeneratePhase2 () then
17821817 regenerateSearchIndex ())
17831818
1784- Serve.signalHotReload.Set () |> ignore
1819+ Serve.refreshEvent.Trigger " full "
17851820 }
17861821 |> Async.Start)
17871822
17881823 // Listen to changes in any input under docs
17891824 for docsWatcher in docsWatchers do
17901825 docsWatcher.IncludeSubdirectories <- true
17911826 docsWatcher.NotifyFilter <- NotifyFilters.LastWrite
1792- docsWatcher.Changed.Add( fun _ -> docsDependenciesChanged.Trigger() )
1827+ docsWatcher.Changed.Add( fun fileEvent -> docsDependenciesChanged.Trigger fileEvent.Name )
17931828
17941829 // When _template.* change rebuild everything
17951830 for templateWatcher in templateWatchers do
@@ -1798,8 +1833,8 @@ type CoreBuildOptions(watch) =
17981833 templateWatcher.Filter <- " *template.html"
17991834 templateWatcher.NotifyFilter <- NotifyFilters.LastWrite
18001835
1801- templateWatcher.Changed.Add( fun _ ->
1802- docsDependenciesChanged.Trigger()
1836+ templateWatcher.Changed.Add( fun fileEvent ->
1837+ docsDependenciesChanged.Trigger fileEvent.Name
18031838 apiDocsDependenciesChanged.Trigger())
18041839
18051840 // Listen to changes in output DLLs
0 commit comments