{"id":29670188,"url":"https://github.com/mpmcintyre/go-again","last_synced_at":"2025-07-22T19:09:14.720Z","repository":{"id":264468948,"uuid":"893354918","full_name":"mpmcintyre/go-again","owner":"mpmcintyre","description":"Hot reload HTML templates without restarting the backend server","archived":false,"fork":false,"pushed_at":"2025-06-24T08:20:47.000Z","size":398,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-18T11:01:49.393Z","etag":null,"topics":["go","golang","hot-reload","live-reload","websocket"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mpmcintyre.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-24T08:04:42.000Z","updated_at":"2025-07-11T10:18:51.000Z","dependencies_parsed_at":"2024-12-31T15:01:08.216Z","dependency_job_id":null,"html_url":"https://github.com/mpmcintyre/go-again","commit_stats":null,"previous_names":["mpmcintyre/go-again"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/mpmcintyre/go-again","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpmcintyre%2Fgo-again","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpmcintyre%2Fgo-again/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpmcintyre%2Fgo-again/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpmcintyre%2Fgo-again/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mpmcintyre","download_url":"https://codeload.github.com/mpmcintyre/go-again/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpmcintyre%2Fgo-again/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266555171,"owners_count":23947477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","hot-reload","live-reload","websocket"],"created_at":"2025-07-22T19:09:14.067Z","updated_at":"2025-07-22T19:09:14.713Z","avatar_url":"https://github.com/mpmcintyre.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go-Again 🔄\n\nGo-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.\n\nPerfect for rapid development of:\n\n- Template modifications\n- CSS styling changes\n- Content updates\n- Layout adjustments\n\n## Features\n\n- 🔥 Hot reloading of HTML templates, css stylesheets, and static files\n- 🎯 Minimal setup required\n- 🌐 WebSocket-based DOM element replacement\n- 📁 Multiple directory watching\n- 🛠️ Framework agnostic (with growing compatibility list)\n- 🪶 Lightweight with minimal dependencies\n- 💾 State Preservation: Maintains client-side state (form inputs, counters, etc.) during updates\n\n## Contents\n\n- [Go-Again 🔄](#go-again-)\n  - [Features](#features)\n  - [Contents](#contents)\n  - [Getting Started](#getting-started)\n  - [Complete Example](#complete-example)\n  - [Framework Compatibility](#framework-compatibility)\n  - [Configuration Options](#configuration-options)\n    - [WithLogs](#withlogs)\n  - [Contributing](#contributing)\n  - [License](#license)\n  - [Support](#support)\n\n## Getting Started\n\nTo install the module, run the following command:\n\n```bash\ngo get github.com/mpmcintyre/go-again\n```\n\nThen import the package in your main routing component:\n\n```go\nimport reloader \"github.com/mpmcintyre/go-again\"\n```\n\nNext, initialize the reloader:\n\n```go\nrel, err := reloader.New(\n    func() { app.LoadHTMLGlob(\"templates/**/*\") },\n    9000,\n    reloader.WithLogs(true),\n)\nif err != nil {\n    log.Fatal(err)\n}\ndefer rel.Close()\n```\n\nNow that the reloader is set up, you can add directories to watch:\n\n```go\nrel.Add(\"templates/components\")\nrel.Add(\"templates/views\")\n```\n\n**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.**\n\nThen add the LiveReload function to your templates:\n\n```go\napp.SetFuncMap(template.FuncMap{\n    \"LiveReload\": rel.TemplateFunc()[\"LiveReload\"],\n})\n```\n\nFinally, include the LiveReload tag in your base template:\n\n```html\n{{ LiveReload }}\n```\n\nYou 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:\n\n```html\n\u003cspan data-client-state data-bind=\"clientCount\"\u003e0\u003c/span\u003e\n```\n\n## Complete Example\n\nCheckout the full examles in the [example](./examples/) directory\n\n```go\npackage main\n\nimport (\n    \"html/template\"\n    \"log\"\n    \"net/http\"\n\n    \"github.com/gin-gonic/gin\"\n    reloader \"github.com/mpmcintyre/go-again\"\n)\n\nfunc main() {\n    app := gin.Default()\n\n    // Initialize reloader\n    rel, err := reloader.New(\n        func() { app.LoadHTMLGlob(\"templates/**/*\") },\n        9000,\n        reloader.WithLogs(true),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer rel.Close()\n\n    // Watch template directories\n    rel.Add(\"templates/components\")\n    rel.Add(\"templates/views\")\n    rel.Add(\"static\")\n\n    // Register LiveReload function\n    app.SetFuncMap(template.FuncMap{\n        \"LiveReload\": rel.TemplateFunc()[\"LiveReload\"],\n    })\n\n    // Load templates\n    app.LoadHTMLGlob(\"templates/**/*\")\n\n    // Routes\n    count := 0\n    app.GET(\"/\", func(g *gin.Context) {\n        count += 1\n        g.HTML(http.StatusOK, \"index.html\", gin.H{\n            \"title\": \"Go Again\",\n            \"count\": count,\n        })\n    })\n\n    app.Run(\":8080\")\n}\n```\n\nWith your template as:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    {{LiveReload}}\n    \u003ctitle\u003e{{ .title }}\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cscript\u003e\n      var clientCount = 0;\n      // Function to update all elements that display the counter\n      function updateCounterDisplays() {\n        document\n          .querySelectorAll('[data-bind=\"clientCount\"]')\n          .forEach((element) =\u003e {\n            element.textContent = clientCount;\n          });\n      }\n\n      // Function to increment counter\n      function incrementCounter() {\n        console.log(\"Increment\");\n        clientCount++;\n        updateCounterDisplays();\n      }\n\n      // Initialize displays when page loads\n      document.addEventListener(\"DOMContentLoaded\", updateCounterDisplays);\n    \u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv\u003e\n      \u003cdiv\u003eServer side count: {{.count}}\u003c/div\u003e\n      \u003cdiv\u003e\n        Client counter value:\n        \u003cspan data-client-state data-bind=\"clientCount\"\u003e0\u003c/span\u003e\n      \u003c/div\u003e\n      \u003cbutton onclick=\"incrementCounter()\"\u003eIncrement Counter\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Framework Compatibility\n\n| Framework   | Compatibility |\n| ----------- | ------------- |\n| Gin         | ✅            |\n| Echo        | ❓            |\n| Fiber       | ❓            |\n| Chi         | ❓            |\n| Buffalo     | ❓            |\n| Beego       | ❓            |\n| Gorilla Mux | ❓            |\n| Iris        | ❓            |\n| Revel       | ❓            |\n| Martini     | ❓            |\n\n✅ - Compatible\n❓ - Not tested yet\n❌ - Not compatible\n\n## Configuration Options\n\n### WithLogs\n\nEnable or disable logging:\n\n```go\nreloader.New(callback, 9000, reloader.WithLogs(true))\n```\n\n## Contributing\n\nContributions are welcome! Feel free to:\n\n1. Fork the repository\n2. Create a feature branch\n3. Submit a Pull Request\n\nPlease ensure you test your changes and update the framework compatibility table if you've verified compatibility with additional frameworks.\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Support\n\nIf you encounter any issues or have questions, please file an issue on GitHub.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpmcintyre%2Fgo-again","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmpmcintyre%2Fgo-again","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpmcintyre%2Fgo-again/lists"}