-
Notifications
You must be signed in to change notification settings - Fork 158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add hot reload for the watch command (#627) #629
Changes from 4 commits
dc59518
632269b
63d7d64
a249175
e7fa334
9e87d40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,16 +218,46 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv | |
|
||
/// Processes and runs Suave server to host them on localhost | ||
module Serve = | ||
//not sure what this was needed for | ||
//let refreshEvent = new Event<_>() | ||
|
||
/// generate the script to inject into html to enable hot reload during development | ||
let generateWatchScript (port:int) = | ||
let tag = """ | ||
<script type="text/javascript"> | ||
var wsUri = "ws://localhost:{{PORT}}/websocket"; | ||
function init() | ||
{ | ||
websocket = new WebSocket(wsUri); | ||
websocket.onclose = function(evt) { onClose(evt) }; | ||
} | ||
function onClose(evt) | ||
{ | ||
console.log('closing'); | ||
websocket.close(); | ||
document.location.reload(); | ||
} | ||
window.addEventListener("load", init, false); | ||
</script> | ||
""" | ||
tag.Replace("{{PORT}}", string port) | ||
|
||
|
||
let refreshEvent = new Event<_>() | ||
let mutable signalHotReload = false | ||
|
||
let socketHandler (webSocket : WebSocket) _ = socket { | ||
while true do | ||
do! | ||
refreshEvent.Publish | ||
|> Control.Async.AwaitEvent | ||
|> Suave.Sockets.SocketOp.ofAsync | ||
do! webSocket.send Text (ByteSegment (Encoding.UTF8.GetBytes "refreshed")) true | ||
let emptyResponse = [||] |> ByteSegment | ||
//not sure what this was needed for | ||
//do! | ||
// refreshEvent.Publish | ||
// |> Control.Async.AwaitEvent | ||
// |> Suave.Sockets.SocketOp.ofAsync | ||
//do! webSocket.send Text (ByteSegment (Encoding.UTF8.GetBytes "refreshed")) true | ||
if signalHotReload then | ||
printfn "Triggering hot reload on the client" | ||
do! webSocket.send Close emptyResponse true | ||
signalHotReload <- false | ||
} | ||
|
||
let startWebServer outputDirectory localPort = | ||
|
@@ -487,6 +517,16 @@ type CoreBuildOptions(watch) = | |
let indxTxt = System.Text.Json.JsonSerializer.Serialize index | ||
File.WriteAllText(Path.Combine(output, "index.json"), indxTxt) | ||
|
||
/// get the hot reload script if running in watch mode | ||
let getLatestWatchScript() = | ||
if watch then | ||
// if running in watch mode, inject hot reload script | ||
[ParamKeys.``fsdocs-watch-script``, Serve.generateWatchScript this.port_option] | ||
else | ||
// otherwise, inject empty replacement string | ||
[ParamKeys.``fsdocs-watch-script``, ""] | ||
|
||
|
||
// Incrementally convert content | ||
let runDocContentPhase1 () = | ||
protect (fun () -> | ||
|
@@ -528,7 +568,7 @@ type CoreBuildOptions(watch) = | |
|
||
let runDocContentPhase2 () = | ||
protect (fun () -> | ||
let globals = getLatestGlobalParameters() | ||
let globals = getLatestWatchScript() @ getLatestGlobalParameters() | ||
latestDocContentPhase2 globals | ||
) | ||
|
||
|
@@ -581,7 +621,7 @@ type CoreBuildOptions(watch) = | |
protect (fun () -> | ||
printfn "" | ||
printfn "Write API Docs:" | ||
let globals = getLatestGlobalParameters() | ||
let globals = getLatestWatchScript() @ getLatestGlobalParameters() | ||
latestApiDocPhase2 globals | ||
regenerateSearchIndex() | ||
) | ||
|
@@ -643,33 +683,40 @@ type CoreBuildOptions(watch) = | |
if not docsQueued then | ||
docsQueued <- true | ||
printfn "Detected change in '%s', scheduling rebuild of docs..." this.input | ||
Async.Start(async { | ||
async { | ||
do! Async.Sleep(300) | ||
lock monitor (fun () -> | ||
docsQueued <- false | ||
if runDocContentPhase1() then | ||
if runDocContentPhase2() then | ||
regenerateSearchIndex() | ||
) }) ) | ||
) | ||
} | ||
|> Async.RunSynchronously | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has changed from Start to RunSynchronously. If it's RunSynchronously then no async is needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, this is unnecessary. I was kind of thrown off by the 'fire and forget' nature of Async.Start (via this) and ditched it without second thought thinking i have to wait until the computation is finished. I missed that the hot reload trigger does not have to happen after the whole block but just after the last function call inside it, so i moved it inside the block now using the original Async.start version again. |
||
Serve.signalHotReload <- true | ||
) | ||
|
||
let apiDocsDependenciesChanged = Event<_>() | ||
apiDocsDependenciesChanged.Publish.Add(fun () -> | ||
if not generateQueued then | ||
generateQueued <- true | ||
printfn "Detected change in built outputs, scheduling rebuild of API docs..." | ||
Async.Start(async { | ||
async { | ||
do! Async.Sleep(300) | ||
lock monitor (fun () -> | ||
generateQueued <- false | ||
if runGeneratePhase1() then | ||
if runGeneratePhase2() then | ||
regenerateSearchIndex()) })) | ||
|
||
regenerateSearchIndex()) | ||
} | ||
|> Async.RunSynchronously | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise here.... |
||
Serve.signalHotReload <- true | ||
) | ||
|
||
// Listen to changes in any input under docs | ||
docsWatcher.IncludeSubdirectories <- true | ||
docsWatcher.NotifyFilter <- NotifyFilters.LastWrite | ||
docsWatcher.Changed.Add (fun _ ->docsDependenciesChanged.Trigger()) | ||
docsWatcher.Changed.Add (fun _ -> docsDependenciesChanged.Trigger()) | ||
|
||
// When _template.* change rebuild everything | ||
templateWatcher.IncludeSubdirectories <- true | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capital "T" at start of line.
Also there is a table of substitutions in content.md I believe, this should be added there