LiveReload
This commit is contained in:
46
internal/examples/livereload/.air.toml
Normal file
46
internal/examples/livereload/.air.toml
Normal file
@@ -0,0 +1,46 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
2
internal/examples/livereload/.gitignore
vendored
Normal file
2
internal/examples/livereload/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
tmp/
|
||||
build-errors.log
|
||||
28
internal/examples/livereload/README.md
Normal file
28
internal/examples/livereload/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Live Reload with Air
|
||||
|
||||
Hot-reloads your Go code and web page.
|
||||
|
||||
## Setup
|
||||
|
||||
If you don't have Air yet:
|
||||
```bash
|
||||
go install github.com/air-verse/air@latest
|
||||
```
|
||||
|
||||
## Run
|
||||
```bash
|
||||
air
|
||||
```
|
||||
|
||||
Then open `http://localhost:3000` in your browser.
|
||||
|
||||
## How It Works
|
||||
|
||||
Air watches your Go files and rebuilds when you make changes.
|
||||
|
||||
LiveReloadPlugin handles browser refresh through a SSE connection at `/dev/reload`. When Air restarts the server, the connection drops, triggering an automatic page reload after 100ms. This only runs on localhost.
|
||||
|
||||
## Files
|
||||
|
||||
- `.air.toml` - Air config
|
||||
- `livereload.go` - Via plugin for browser auto-reload
|
||||
57
internal/examples/livereload/livereload.go
Normal file
57
internal/examples/livereload/livereload.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-via/via"
|
||||
"github.com/go-via/via/h"
|
||||
)
|
||||
|
||||
func LiveReloadPlugin(v *via.V) {
|
||||
v.HandleFunc("GET /dev/reload", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
<-r.Context().Done()
|
||||
})
|
||||
}
|
||||
|
||||
func liveReloadScript() h.H {
|
||||
return h.Script(h.Raw(`
|
||||
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||
const evtSource = new EventSource('/dev/reload');
|
||||
let overlay = null;
|
||||
let showTimer = null;
|
||||
|
||||
evtSource.onerror = () => {
|
||||
evtSource.close();
|
||||
|
||||
showTimer = setTimeout(() => {
|
||||
if (!overlay) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(200, 200, 200, 0.95); padding: 20px 40px; border-radius: 8px; color: #333; font-size: 24px; z-index: 999999; font-family: -apple-system, sans-serif;';
|
||||
overlay.textContent = '🔌 Reconnecting...';
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
(async function poll() {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
try {
|
||||
const res = await fetch('/', { method: 'HEAD', signal: AbortSignal.timeout(1000) });
|
||||
if (res.ok) {
|
||||
clearTimeout(showTimer);
|
||||
if (overlay) overlay.remove();
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
}
|
||||
})();
|
||||
};
|
||||
}
|
||||
`))
|
||||
}
|
||||
46
internal/examples/livereload/main.go
Normal file
46
internal/examples/livereload/main.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-via/via"
|
||||
"github.com/go-via/via/h"
|
||||
)
|
||||
|
||||
type Counter struct{ Count int }
|
||||
|
||||
func main() {
|
||||
v := via.New()
|
||||
|
||||
LiveReloadPlugin(v)
|
||||
|
||||
v.Config(via.Options{
|
||||
DocumentTitle: "Live Reload",
|
||||
DocumentHeadIncludes: []h.H{
|
||||
liveReloadScript(),
|
||||
},
|
||||
})
|
||||
|
||||
v.Page("/", func(c *via.Context) {
|
||||
data := Counter{Count: 0}
|
||||
step := c.Signal(1)
|
||||
|
||||
increment := c.Action(func() {
|
||||
data.Count += step.Int()
|
||||
c.Sync()
|
||||
})
|
||||
|
||||
c.View(func() h.H {
|
||||
return h.Div(h.Class("container"),
|
||||
h.H1(h.Text("Live Reload")),
|
||||
h.P(h.Textf("Count: %d", data.Count)),
|
||||
h.P(h.Span(h.Text("Step: ")), h.Span(step.Text())),
|
||||
h.Label(
|
||||
h.Text("Update Step: "),
|
||||
h.Input(h.Type("number"), step.Bind()),
|
||||
),
|
||||
h.Button(h.Text("Increment"), increment.OnClick()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
v.Start(":3000")
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func (s *signal) Err() error {
|
||||
return s.err
|
||||
}
|
||||
|
||||
// Bind binds this signal to an imput element. When the imput changes
|
||||
// Bind binds this signal to an input element. When the input changes
|
||||
// its value the signal updates in real-time in the browser.
|
||||
//
|
||||
// Example:
|
||||
|
||||
5
via.go
5
via.go
@@ -75,6 +75,9 @@ func (v *V) Config(cfg Options) {
|
||||
if cfg.LogLvl != v.cfg.LogLvl {
|
||||
v.cfg.LogLvl = cfg.LogLvl
|
||||
}
|
||||
if cfg.DocumentTitle != "" {
|
||||
v.cfg.DocumentTitle = cfg.DocumentTitle
|
||||
}
|
||||
if cfg.DocumentHeadIncludes != nil {
|
||||
v.cfg.DocumentHeadIncludes = cfg.DocumentHeadIncludes
|
||||
}
|
||||
@@ -142,6 +145,8 @@ func (v *V) registerCtx(id string, c *Context) {
|
||||
// }
|
||||
|
||||
func (v *V) getCtx(id string) (*Context, error) {
|
||||
v.contextRegistryMutex.RLock()
|
||||
defer v.contextRegistryMutex.RUnlock()
|
||||
if c, ok := v.contextRegistry[id]; ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user