https://github.com/mpmcintyre/go-again
Hot reload HTML templates without restarting the backend server
https://github.com/mpmcintyre/go-again
go golang hot-reload live-reload websocket
Last synced: 11 months ago
JSON representation
Hot reload HTML templates without restarting the backend server
- Host: GitHub
- URL: https://github.com/mpmcintyre/go-again
- Owner: mpmcintyre
- License: mit
- Created: 2024-11-24T08:04:42.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-06-24T08:20:47.000Z (12 months ago)
- Last Synced: 2025-07-18T11:01:49.393Z (11 months ago)
- Topics: go, golang, hot-reload, live-reload, websocket
- Language: Go
- Homepage:
- Size: 389 KB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# Go-Again ð
Go-Again is a lightweight live-update solution for Go web applications. It automatically updates your browser content when template files change, making development faster and more efficient. Unlike server hot-reloading tools (like Air) that require restarting the entire Go server, Go-Again only updates the affected content while keeping your server running and preserving client-side state.
Perfect for rapid development of:
- Template modifications
- CSS styling changes
- Content updates
- Layout adjustments
## Features
- ðĨ Hot reloading of HTML templates, css stylesheets, and static files
- ðŊ Minimal setup required
- ð WebSocket-based DOM element replacement
- ð Multiple directory watching
- ð ïļ Framework agnostic (with growing compatibility list)
- ðŠķ Lightweight with minimal dependencies
- ðū State Preservation: Maintains client-side state (form inputs, counters, etc.) during updates
## Contents
- [Go-Again ð](#go-again-)
- [Features](#features)
- [Contents](#contents)
- [Getting Started](#getting-started)
- [Complete Example](#complete-example)
- [Framework Compatibility](#framework-compatibility)
- [Configuration Options](#configuration-options)
- [WithLogs](#withlogs)
- [Contributing](#contributing)
- [License](#license)
- [Support](#support)
## Getting Started
To install the module, run the following command:
```bash
go get github.com/mpmcintyre/go-again
```
Then import the package in your main routing component:
```go
import reloader "github.com/mpmcintyre/go-again"
```
Next, initialize the reloader:
```go
rel, err := reloader.New(
func() { app.LoadHTMLGlob("templates/**/*") },
9000,
reloader.WithLogs(true),
)
if err != nil {
log.Fatal(err)
}
defer rel.Close()
```
Now that the reloader is set up, you can add directories to watch:
```go
rel.Add("templates/components")
rel.Add("templates/views")
```
**Note: If you are using a tool like [air](https://github.com/air-verse/air) you must ensure that the templates directories are ignored in the .air.toml configuration files.**
Then add the LiveReload function to your templates:
```go
app.SetFuncMap(template.FuncMap{
"LiveReload": rel.TemplateFunc()["LiveReload"],
})
```
Finally, include the LiveReload tag in your base template:
```html
{{ LiveReload }}
```
You can also tell the HMR script to ignore client-side state values to preserve the view of a value using the `data-client-state` tag:
```html
0
```
## Complete Example
Checkout the full examles in the [example](./examples/) directory
```go
package main
import (
"html/template"
"log"
"net/http"
"github.com/gin-gonic/gin"
reloader "github.com/mpmcintyre/go-again"
)
func main() {
app := gin.Default()
// Initialize reloader
rel, err := reloader.New(
func() { app.LoadHTMLGlob("templates/**/*") },
9000,
reloader.WithLogs(true),
)
if err != nil {
log.Fatal(err)
}
defer rel.Close()
// Watch template directories
rel.Add("templates/components")
rel.Add("templates/views")
rel.Add("static")
// Register LiveReload function
app.SetFuncMap(template.FuncMap{
"LiveReload": rel.TemplateFunc()["LiveReload"],
})
// Load templates
app.LoadHTMLGlob("templates/**/*")
// Routes
count := 0
app.GET("/", func(g *gin.Context) {
count += 1
g.HTML(http.StatusOK, "index.html", gin.H{
"title": "Go Again",
"count": count,
})
})
app.Run(":8080")
}
```
With your template as:
```html
{{LiveReload}}
{{ .title }}
var clientCount = 0;
// Function to update all elements that display the counter
function updateCounterDisplays() {
document
.querySelectorAll('[data-bind="clientCount"]')
.forEach((element) => {
element.textContent = clientCount;
});
}
// Function to increment counter
function incrementCounter() {
console.log("Increment");
clientCount++;
updateCounterDisplays();
}
// Initialize displays when page loads
document.addEventListener("DOMContentLoaded", updateCounterDisplays);
Server side count: {{.count}}
Client counter value:
0
Increment Counter
```
## Framework Compatibility
| Framework | Compatibility |
| ----------- | ------------- |
| Gin | â
|
| Echo | â |
| Fiber | â |
| Chi | â |
| Buffalo | â |
| Beego | â |
| Gorilla Mux | â |
| Iris | â |
| Revel | â |
| Martini | â |
â
- Compatible
â - Not tested yet
â - Not compatible
## Configuration Options
### WithLogs
Enable or disable logging:
```go
reloader.New(callback, 9000, reloader.WithLogs(true))
```
## Contributing
Contributions are welcome! Feel free to:
1. Fork the repository
2. Create a feature branch
3. Submit a Pull Request
Please ensure you test your changes and update the framework compatibility table if you've verified compatibility with additional frameworks.
## License
MIT License - see LICENSE file for details.
## Support
If you encounter any issues or have questions, please file an issue on GitHub.