Skip to content

Commit 676a00f

Browse files
authored
Merge pull request #845 from nojaf/css-watch
Only reload css file when changed.
2 parents f47b9d0 + 2d50c15 commit 676a00f

File tree

1 file changed

+60
-25
lines changed

1 file changed

+60
-25
lines changed

src/fsdocs-tool/BuildCommand.fs

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace fsdocs
22

3+
open System.Collections.Concurrent
34
open CommandLine
45

56
open System
@@ -25,6 +26,7 @@ open Suave.Sockets.Control
2526
open Suave.WebSocket
2627
open Suave.Operators
2728
open Suave.Filters
29+
open Suave.Logging
2830
open 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
629631
module 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

Comments
 (0)