├── .gitignore ├── go.mod ├── flake.nix ├── go.sum ├── flake.lock ├── reload.js ├── LICENSE ├── reload-server.go ├── README.md └── reload.go /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/danderson/reload 2 | 3 | go 1.21.5 4 | 5 | require github.com/gorilla/websocket v1.5.1 6 | 7 | require golang.org/x/net v0.17.0 // indirect 8 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | }; 5 | 6 | outputs = { self, nixpkgs }: { 7 | devShell.x86_64-linux = with nixpkgs.legacyPackages.x86_64-linux; mkShell { 8 | buildInputs = [ 9 | go 10 | gopls 11 | ]; 12 | }; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 2 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 3 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 4 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 5 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1704161960, 6 | "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "63143ac2c9186be6d9da6035fa22620018c85932", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /reload.js: -------------------------------------------------------------------------------- 1 | function (cookie) { 2 | var url = new URL(document.currentScript.src); 3 | url.protocol = url.protocol == "https:" ? "wss:" : "ws:"; 4 | 5 | var retry = 50; // ms 6 | 7 | function connect() { 8 | retry = Math.min(retry*2, 2000); 9 | 10 | const socket = new WebSocket(url); 11 | 12 | socket.onmessage = (event) => { 13 | if (event.data !== "") { 14 | if (cookie !== event.data) { 15 | console.log("[Hot Reload] Reloading"); 16 | socket.close(); 17 | location.reload(); 18 | } 19 | } 20 | 21 | socket.send(""); 22 | }; 23 | 24 | socket.onopen = () => { 25 | retry = 100; 26 | console.log("[Hot Reload] Connected"); 27 | }; 28 | 29 | socket.onclose = () => { 30 | const id = setTimeout(function () { 31 | clearTimeout(id); 32 | connect(); 33 | }, retry); 34 | }; 35 | } 36 | 37 | connect(); 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Dave Anderson 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /reload-server.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | // This is just a test server that demonstrates using the reload 4 | // handler. 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/danderson/reload" 14 | ) 15 | 16 | func main() { 17 | rl := &reload.Reloader{} 18 | 19 | http.HandleFunc("/", index) 20 | http.HandleFunc("/reload", func(w http.ResponseWriter, r *http.Request) { 21 | rl.Reload() 22 | http.Redirect(w, r, "/reload-ui", http.StatusFound) 23 | }) 24 | http.HandleFunc("/reload-ui", reloadUI) 25 | http.Handle("/live", rl) 26 | http.ListenAndServe(":8080", nil) 27 | } 28 | 29 | func index(w http.ResponseWriter, r *http.Request) { 30 | w.Header().Set("Content-Type", "text/html") 31 | fmt.Fprintf(w, ` 32 | 33 |
34 | 35 | 36 | 37 |At the tone, the time is %v, Coordinated Universal Time
38 | 39 | 40 | 41 | `, 42 | time.Now().UTC().Format("03:04:05")) 43 | } 44 | 45 | func reloadUI(w http.ResponseWriter, r *http.Request) { 46 | w.Header().Set("Content-Type", "text/html") 47 | io.WriteString(w, ` 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | `) 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Reload for Go 2 | 3 | [](https://pkg.go.dev/github.com/danderson/reload) 4 | 5 | This package provides very simple live reloading for Go web 6 | servers. It is intended purely for development, and is not specialized 7 | to any web or client framework. The reload is literally an automated 8 | push of the refresh button in the browser, nothing more. 9 | 10 | To use, serve the reloader from some URL in your web server: 11 | 12 | ```go 13 | rl := &reload.Reloader{} 14 | mux.Handle("/.magic/live", rl) 15 | ``` 16 | 17 | Then reference it through a script tag in your HTML: 18 | 19 | ```html 20 | 21 | 22 | 23 | 24 | 25 | 26 |27 | This page will refresh when the server changes, or when 28 | something visits /.magic/reload. 29 |
30 | 31 | 32 | ``` 33 | 34 | Every page that loads the script will reload itself whenever the 35 | server restarts, or when the server invokes `Reload` on the live 36 | handler. For example, you can use the latter to make a magic URL that 37 | reloads all clients: 38 | 39 | ```go 40 | mux.HandleFunc("/.magic/reload", func(w http.ResponseWriter, r *http.Request) { 41 | rl.Reload() 42 | }) 43 | ``` 44 | 45 | ## Credit 46 | 47 | Thank you to [Andy Dote](https://andydote.co.uk/) and his [blog post 48 | about hot reloading in 49 | Go](https://andydote.co.uk/2023/11/15/hot-reload-for-serverside-rendering/). I 50 | had the exact problem his post solves, and this library is a mild 51 | variation on the solution he presents there. 52 | -------------------------------------------------------------------------------- /reload.go: -------------------------------------------------------------------------------- 1 | // Package reload provides an HTTP handler that implements simple live 2 | // reloading for Go web servers. 3 | // 4 | // To use, register this handler in your HTTP server, then load it via 5 | // a