Comments (11)
I've had chance to have a crack at this.
The fsnotify
implementation didn't produce all events on my Mac. From reading around there's a long outstanding issue with it around needing a backup process and my understanding is that some frameworks don't rely on it, and use a 250ms delay between os.Walk
instead. I've implemented that, and it seems to work well. I've put in some basic ignore rules (node_modules, .git etc.) to make it skip directories that should never contain templ files.
Unfortunately, the rod
implementation produces panics on my Mac. Even, if I closed all the browsers and let it try again. Maybe it's because I have multiple Chrome profiles, but it didn't seem reliable to me for the use case, so I've attempted the proxy approach and used the browser
package to auto-open the proxy endpoint in the browser.
I have a working version in the https://github.com/a-h/templ/tree/hot_reload branch.
I can run templ generate --watch --proxy="http://localhost:8080" --cmd="runtest"
and it will start a proxy on localhost through to localhost:8080
, and, provided any response has a Content-Type
of text/html
and the HTML contains a </body>
tag, the proxy will inject an automatic reload script.
The reload script connects to the proxy's SSE endpoint. Any changes to templ
files results in a recompilation, this recompilation then sends a message to the SSE handler, and any connected clients then execute window.location.reload()
.
I haven't had time to tidy up the code much, but it works on a basic test web server (runtest
).
I've made a PR - feel free to comment: #130
from templ.
Do make sure to read the tips and tricks for fsnotify. Sometimes IDEs do sneaky things to provide autosave of unsaved code. For instance, you might see it not notice changes as often as you expect because it flipped which file descriptor is pointed at which file. The same inode you were watching might no longer be pointed at the file it was pointed to. The behavior of fsnotify is generally correct, but the low level reality of what's happening in the filesystem can be surprising. Make sure to test with a Jetbrains IDE and VS Code separately to try to identify any unexpected behaviors.
from templ.
https://github.com/loov/watchrun might be a good one to do this.
I used this to do exactly what the Issue is about - watch folders and gen code when the files change.
There are some examples and its pretty adaptable to different needs too.
this uses polling btw. 3 times a second. Why ? different OS aspects make fsnotify a bit of a pita. mac has some issues, windows other etc .. So the author went with simple polling, which for a Design tool is perfectly fine. Its as fast as notify for human me - you don't notice its delay.
You can have vscode and LSP ticking along and this will gen golang from the templates. It's pretty simple and isolated
@a-h " templ could ship with HTTP middleware that implements hot reload" - yes exactly what i was thinking. So then you can change the templates at runtime if you also introduce wasm. the wasm with wazero is a really good stack - No CGO, run anywhere
https://github.com/bots-garden/capsule does exactly this replacing AWS lambda, etc
Because ts wasm and http, you can even do Client side templating / Rendering in a Service worker then and watch via the new Web File System API at Dev time.. I am really getting ahead of myself though !!
from templ.
The documentation suggests using air - https://templ.guide/commands-and-tools/hot-reload
But it's installing another tool, and having to configure it. Running
templ generate --watch
would be a better user experience.It would make #80 irrelevant, since it would be able to maintain internal state such as the last-updated time. https://github.com/fsnotify/fsnotify seems to be the library to use.
Since templ doesn't know how the web server is configured, it can't easily inject client side code to force a refresh.
But... templ could ship with HTTP middleware that implements hot reload. The middleware would be able to set the target directory that contains the templates and use fsnotify to watch it, or figure it the target directory from
os.Args
, runtempl generate
and then start a replacement of itself based on theos.Args
. Since it's HTTP middleware, it could parse the output HTML and inject stuff into the page.Alternatively, it could break the problem into two parts... so that
templ generate --watch
starts a web server on localhost which controls hot reload, and it also ships a middleware component that injects a script to connect to thetempl generate --watch
port (e.g. http://localhost:14734/) and deals with CORS.
Like the thinking here - clean and flexible.
from templ.
I use https://github.com/go-rod/rod and a taskfile watch
from templ.
@delaneyj I think the main proposal here is to add some sort of middleware that not only will rebuild templates but will also refresh the browser during development. Similar to this proxy: https://github.com/diamondburned/saq
from templ.
@delaneyj I think the main proposal here is to add some sort of middleware that not only will rebuild templates but will also refresh the browser during development. Similar to this proxy: https://github.com/diamondburned/saq
https://github.com/diamondburned/saq#supported-platforms:
"
saq only works on Linux due to its dependency on illarion/gonotify.
"
from templ.
@delaneyj I think the main proposal here is to add some sort of middleware that not only will rebuild templates but will also refresh the browser during development. Similar to this proxy: https://github.com/diamondburned/saq
Yeah that's what's I'm doing, the combination of task -w
and rod
is how I'm doing hot reload
from templ.
@delaneyj Oh nice!
from templ.
func runHotReload(port int, onStartPath string) toolbelt.CtxErrFunc {
return func(ctx context.Context) error {
onStartPath = strings.TrimPrefix(onStartPath, "/")
localHost := fmt.Sprintf("http://localhost:%d", port)
localURLToLoad := fmt.Sprintf("%s/%s", localHost, onStartPath)
// Make sure page is ready before we start
backoff := backoff.NewExponentialBackOff()
for {
if _, err := http.Get(localURLToLoad); err == nil {
break
}
d := backoff.NextBackOff()
log.Printf("Server not ready. Retrying in %v", d)
time.Sleep(d)
}
// Launch browser in user mode, so we can reuse the same browser session
wsURL := launcher.NewUserMode().MustLaunch()
browser := rod.New().ControlURL(wsURL).MustConnect().NoDefaultDevice()
// Get the current pages
pages, err := browser.Pages()
if err != nil {
return fmt.Errorf("failed to get pages: %w", err)
}
var page *rod.Page
for _, p := range pages {
info, err := p.Info()
if err != nil {
return fmt.Errorf("failed to get page info: %w", err)
}
// If we already have the page open, just reload it
if strings.HasPrefix(info.URL, localHost) {
p.MustActivate().MustReload()
page = p
break
}
}
if page == nil {
// Otherwise, open a new page
page = browser.MustPage(localURLToLoad)
}
// time.Sleep(1 * time.Second)
// page.Reload()
// page.Reload()
// page.Reload()
slog.Info("page loaded", "url", localURLToLoad, "page", page.TargetID)
return nil
}
}
And within the taskfile
racer_dev:
desc: Server hot reload
sources:
- "**/*.go"
deps:
- racer_tw
cmds:
- killall -q foo_bin || echo "Process was not running."
- go mod tidy
- go build -o ./foo_bin cmd/foo/*.go
- ./foo_bin
```
from templ.
To summarise... (correct me if I'm wrong)
My understanding is that rod
automates the browser using a browser automation API, and that side-steps the need to inject anything into the web page itself, so for reload, that looks like the way.
To generate the templates, compile the code, stop the current web server, and replace it with the new one, templ would need to know the command to run. @delaneyj uses a taskfile to do that.
@blinkinglight has done a PR that focuses on watching the templ files, and re-runs the templ compile step: #97 - it uses fsnotify, and shows how it could fit in. I think that I can take that forward, might want to refactor to reuse more code between generate and watch.
Plan
Implement templ generate --watch --reload="go run main.go" --url="http://localhost:8080"
command.
- templ immediately generates templates.
- On first run:
- templ will execute the
reload
command. If--reload
is provided with an empty string,go run *.go
will be used as default. - templ will capture stdout/stderr of the
reload
process and display it.
- templ will execute the
- On subsequent runs:
- templ will log that it's terminating the running process, and send sigterm to the process it started. If the process fails to stop, templ will log that, and after 3 seconds, kill the process, also logging that. In future versions, it might provide a link to a templ.guide page that shows how to do graceful shutdown of web apps.
- templ will execute the
reload
command again.
- If an
--url
flag is passed, templ will use rod to open up the browser, or reload the existing one. - On first run, templ will start watching the current directory tree with
fsnotify
. Maybe it might be a good idea to restart this after each generation, not sure. Not sure iffsnotify
ignores.git
etc. - If the files change, templ goes back to the start of this process.
from templ.
Related Issues (20)
- Investigate issue raised in vscode-go HOT 1
- LSP: Autocomplete seems broken HOT 2
- VS Code: Request textDocument/codeAction failed. HOT 1
- docs: TailwindCSS setup autocomplete no longer work HOT 8
- Vscode plugin crash HOT 4
- [question] go 1.22 iterators / rangefunc HOT 2
- Release `v0.2.731` breaks Mason.nvim integration HOT 5
- using context with implicit vs explicit import HOT 3
- Error with VSCode: Request textDocument/codeAction failed HOT 1
- LSP on Vscode Broken HOT 4
- Templ gopls functionality seems to still be broken with gopls `v0.16.x` and does not work with gopls `v0.15.3` anymore HOT 3
- Language server seg fault on VSCode HOT 13
- Deeply nested structs / maps are inaccessible and unusable - Ex data.Form.FieldErrors.NewEmail undefined (type validator.FieldErrors has no field or method NewEmail) HOT 5
- bug: "github.com/a-h/templ/runtime" imported as templruntime and not used HOT 2
- Getting this error while using the latest version or templ and gopls HOT 11
- Formatting a parameterized javascript function
- Templ config for passsing additional variables to component HOT 1
- Proxy not ready, retrying infinitely HOT 2
- Improved dev flow
- Proposal: Introduce JsGenericVar for Event and Element Access in element event handlers HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from templ.