Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: complete rewrite of the generation command, adds fsnotify hot reload #470

Merged
merged 17 commits into from
Jan 30, 2024

Conversation

a-h
Copy link
Owner

@a-h a-h commented Jan 28, 2024

This PR updates the generation command.

This could solve various issues, including:

This builds on top of the excellent PR from @stephenafamo (#366) which added the dev mode facility where Go recompilation isn't required if only the text changes.

Here's a video showing the updated experience:

templ.hot.reload.-.fsnotify.and.text.updates.mp4

@a-h a-h requested a review from joerdav January 28, 2024 20:19
@a-h a-h marked this pull request as ready for review January 28, 2024 20:19
@joerdav
Copy link
Collaborator

joerdav commented Jan 29, 2024

Still in the process of grokking the bulk of this PR! An initial comment would be on the new level flag, I wonder if a more standard flag approach such as -v and -vv would be more intuitive.

@a-h
Copy link
Owner Author

a-h commented Jan 29, 2024

Explanation of the logic in this file:

https://github.com/a-h/templ/blob/1a1ba9731926fe6382bb45d4d9c773ad75632a3c/cmd/templ/generatecmd/cmd.go

There's a pushHandler goroutine that walks the filesystem / uses fsnotify and pushes updates into the events and errs channel. If there's a fatal error, everything shuts down.

There's also a eventHandler goroutine that listens to the events and runs the templ generate operations. It spins up multiple goroutines using a semaphore pattern to limit the number of concurrent processes. This code checks to see if the any Go code or strings were updated, and if they were, notifies the post-processing queue.

The post-processing queue "debounces" messages from the generation queue. It waits until the queue has been empty for 100ms before running any configured commands, or triggering an SSE in the browser to reload the web page.

Once these background queues are started, reading from the errs channel is on the foreground goroutine. Fatal errors stop execution, while other errors are simply logged.

Context cancellation causes the pushHandler to close the errs and events channels as it exits. This causes the eventHandler to close which, in turn, closes the post-processing queue, which causes the post-processing handler to close.

Finally, the process waits until all background goroutines are finished, before terminating all started commands.

I've added a structured logger which can be activated by using the -level flag to set to debug if you want to see all of the detail.

@FACorreiaa
Copy link

Im guessing this will also work if Im running the app from a Docker container for testing ? Assuming it can be used with air.

@joerdav
Copy link
Collaborator

joerdav commented Jan 29, 2024

@FACorreiaa I don't think this would interop with air too well, it's almost an alternative. Air does the watching for you, and will rebuild your code anyway if I understand it correctly.

I think we would have to have a mode where templ only regenerates templ code, and then you could leave the recompiling of Go when required to Air.

@a-h
Copy link
Owner Author

a-h commented Jan 29, 2024

If you run templ generate --watch it generates dev mode code which - creates/updates _templ.txt files with the strings that make up the HTML, and Go code that reads from the file instead of having the strings embedded in the output code.

If you get air to run templ generate --watch continuously, that would be ideal (if you want to use air to do other things). The key thing is that if you have templ generate --watch mode running, air shouldn't restart your program if *.templ or _templ.txt files are updated, but should if any *.go files are updated.

If you run templ generate --watch --cmd="", then templ is smart enough to only stop and start the cmd if that's the case.

If you then add templ generate --watch --cmd="go run ." --proxy="http://localhost:8080" templ starts a proxy and opens the browser to that page. The proxy adds some JS to the page that initiates a background SSE (server-sent events) session. If the text on a page is updated, or the Go code changes and the cmd has to reload, the proxy mode then sends an SSE to the browser triggering a page reload (i.e. you don't need to reload the web browser page manually to see the changes).

@joerdav
Copy link
Collaborator

joerdav commented Jan 29, 2024

Looks good to me!

@stefanohrmann
Copy link

if errors.Is(err, context.Canceled) {
cmd.Log.Debug("Context cancelled, exiting")
return nil
}

Where is this relevant? I didn't see any code that pushes ctx.Err() to the errs channel.

@a-h
Copy link
Owner Author

a-h commented Jan 29, 2024

if errors.Is(err, context.Canceled) {
cmd.Log.Debug("Context cancelled, exiting")
return nil
}

Where is this relevant? I didn't see any code that pushes ctx.Err() to the errs channel.

I don't think there is any code that does that. I think I can remove it.

@a-h a-h merged commit 1bf6faa into main Jan 30, 2024
6 checks passed
@a-h a-h deleted the fsnotify branch January 30, 2024 08:45
SpencerMalone added a commit to SpencerMalone/templ that referenced this pull request Feb 29, 2024
Due to the inclusion of log/slog in a-h#470, this is the new minimum version
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants