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 reloading example #52

Merged
merged 2 commits into from
May 3, 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
17 changes: 17 additions & 0 deletions example/w-live-reloading/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `w-live-reload`

<br>

This example demonstrates how to setup live reloading of the client HTML content.

It works by injecting a script in the HTML pages sent to clients that will initiate a WebSocket.

When the server restarts, the WebSocket connection is lost, at which point, the client will try to reconnect every 500ms for 5s.
If within these 5s the client is able to reconnect to the server, it will trigger a reload of the page.

This example plays very well with `w-fswatch`, which demonstrates how to restart the server every time the filesystem is modified.
When integrating the two examples, one is able to have a setup where the clients' pages are reloaded every time a file is modified.

<br>

[Up to the example index](../#examples)
4 changes: 4 additions & 0 deletions example/w-live-reloading/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(executable
(name live_reloading)
(libraries dream lambdasoup)
(preprocess (pps lwt_ppx)))
1 change: 1 addition & 0 deletions example/w-live-reloading/dune-project
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(lang dune 2.0)
10 changes: 10 additions & 0 deletions example/w-live-reloading/esy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"dependencies": {
"@opam/dream": "aantron/dream:dream.opam",
"@opam/dune": "^2.0",
"ocaml": "4.12.x"
},
"scripts": {
"start": "dune exec --root . ./live_reloading.exe"
}
}
89 changes: 89 additions & 0 deletions example/w-live-reloading/live_reloading.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
let livereload_script
?(retry_interval_ms = 500)
?(max_retry_ms = 5000)
?(route = "/_livereload")
()
=
Printf.sprintf
{js|
var socketUrl = "ws://" + location.host + "%s"
var s = new WebSocket(socketUrl);

s.onopen = function(even) {
console.log("WebSocket connection open.");
};

s.onclose = function(even) {
console.log("WebSocket connection closed.");
const innerMs = %i;
const maxMs = %i;
const maxAttempts = Math.round(maxMs / innerMs);
let attempts = 0;
function reload() {
attempts++;
if(attempts > maxAttempts) {
console.error("Could not reconnect to dev server.");
return;
}

s2 = new WebSocket(socketUrl);

s2.onerror = function(event) {
setTimeout(reload, innerMs);
};

s2.onopen = function(event) {
location.reload();
};
};
reload();
};

s.onerror = function(event) {
console.error("WebSocket error observed:", event);
};
|js}
route
retry_interval_ms
max_retry_ms

let inject_livereload_script
?(reload_script = livereload_script ())
()
(next_handler : Dream.request -> Dream.response Lwt.t)
(request : Dream.request)
: Dream.response Lwt.t
=
let%lwt response = next_handler request in
match Dream.header "Content-Type" response with
| Some "text/html" | Some "text/html; charset=utf-8" ->
let%lwt body = Dream.body response in
let soup = Soup.parse body in
let open Soup.Infix in
(match soup $? "head" with
| None ->
Lwt.return response
| Some head ->
Soup.create_element "script" ~inner_text:reload_script
|> Soup.append_child head;
Lwt.return (Dream.with_body (Soup.to_string soup) response))
| _ ->
Lwt.return response

let livereload_route ?(path = "/_livereload") () =
Dream.get path (fun _ ->
Dream.websocket (fun socket ->
Lwt.bind (Dream.receive socket) (fun _ ->
Dream.close_websocket socket)))

let () =
Dream.run
@@ Dream.logger
@@ inject_livereload_script ()
@@ Dream.router [
livereload_route ();
Dream.get "/"
(fun _ ->
Dream.html "Good morning, world!");
]
@@ Dream.not_found