Code Monkey home page Code Monkey logo

Comments (11)

a-h avatar a-h commented on July 23, 2024 2

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.

PaluMacil avatar PaluMacil commented on July 23, 2024 1

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.

gedw99 avatar gedw99 commented on July 23, 2024

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.

gedw99 avatar gedw99 commented on July 23, 2024

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, run templ generate and then start a replacement of itself based on the os.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 the templ generate --watch port (e.g. http://localhost:14734/) and deals with CORS.

Like the thinking here - clean and flexible.

from templ.

delaneyj avatar delaneyj commented on July 23, 2024

I use https://github.com/go-rod/rod and a taskfile watch

from templ.

joerdav avatar joerdav commented on July 23, 2024

@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.

gedw99 avatar gedw99 commented on July 23, 2024

@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 avatar delaneyj commented on July 23, 2024

@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.

joerdav avatar joerdav commented on July 23, 2024

@delaneyj Oh nice!

from templ.

delaneyj avatar delaneyj commented on July 23, 2024

@joerdav

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.

a-h avatar a-h commented on July 23, 2024

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.
  • 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 if fsnotify ignores .git etc.
  • If the files change, templ goes back to the start of this process.

from templ.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.