Skip to content
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

Merged
merged 6 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<!-- BEGIN SEARCH BOX: this adds support for the search box -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.css" />
<!-- END SEARCH BOX: this adds support for the search box -->

{{fsdocs-watch-script}}
</head>

<body>
Expand Down
2 changes: 1 addition & 1 deletion docs/content.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ See [Styling](styling.html) for information about template parameters and stylin
| `fsdocs-page-title` | First h1 heading in literate file. Generated for API docs |
| `fsdocs-source` | Original literate script or markdown source |
| `fsdocs-tooltips` | Generated hidden div elements for tooltips |

| `fsdocs-watch-script` | The websocket script used in watch mode to trigger hot reload |

The following substitutions are extracted from your project files and may or may not be used by the default
template:
Expand Down
6 changes: 6 additions & 0 deletions docs/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ If you write a new theme by CSS styling please contribute it back to FSharp.Form
You can do advanced styling by creating a new template. Add a file `docs/_template.html`, likely starting
with the existing default template.

To enable hot reload during development with `fsdocs watch` in a custom `_template.html` file, make sure to add the following line to your `<head>` tag:

```
{{fsdocs-watch-script}}
```

> NOTE: There is no guarantee that your template will continue to work with future versions of F# Formatting.
> If you do develop a good template please consider contributing it back to F# Formatting.

Expand Down
2 changes: 1 addition & 1 deletion docs/templates/leftside/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<!-- BEGIN SEARCH BOX: this adds support for the search box -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.css" />
<!-- END SEARCH BOX: this adds support for the search box -->

{{fsdocs-watch-script}}
</head>

<body>
Expand Down
90 changes: 69 additions & 21 deletions src/FSharp.Formatting.CommandTool/BuildCommand.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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 () ->
Expand Down Expand Up @@ -528,7 +568,7 @@ type CoreBuildOptions(watch) =

let runDocContentPhase2 () =
protect (fun () ->
let globals = getLatestGlobalParameters()
let globals = getLatestWatchScript() @ getLatestGlobalParameters()
latestDocContentPhase2 globals
)

Expand Down Expand Up @@ -581,7 +621,7 @@ type CoreBuildOptions(watch) =
protect (fun () ->
printfn ""
printfn "Write API Docs:"
let globals = getLatestGlobalParameters()
let globals = getLatestWatchScript() @ getLatestGlobalParameters()
latestApiDocPhase2 globals
regenerateSearchIndex()
)
Expand Down Expand Up @@ -643,33 +683,41 @@ 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()
) }) )
docsQueued <- false
if runDocContentPhase1() then
if runDocContentPhase2() then
regenerateSearchIndex()
)
Serve.signalHotReload <- true
}
|> Async.Start
)

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()) }))

generateQueued <- false
if runGeneratePhase1() then
if runGeneratePhase2() then
regenerateSearchIndex()
)
Serve.signalHotReload <- true
}
|> Async.Start
)

// 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
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Formatting.Common/Templating.fs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ module ParamKeys =
/// A parameter key known to FSharp.Formatting
let ``fsdocs-tooltips`` = ParamKey "fsdocs-tooltips"

/// A parameter key known to FSharp.Formatting
let ``fsdocs-watch-script`` = ParamKey "fsdocs-watch-script"

module internal SimpleTemplating =

#if NETSTANDARD2_0
Expand Down
5 changes: 3 additions & 2 deletions tests/FSharp.Literate.Tests/files/template.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8">
<!--
The {page-title} parameters will be replaced with the
Expand All @@ -18,7 +18,8 @@
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
{{fsdocs-watch-script}}
</head>
<body>
<div class="container">
<div class="row" style="margin-top:30px">
Expand Down