Ohm
Ohm•11mo ago

API hooks during restart for deno run --watch

Hello! I thought it would be super fun to further minimize repetitive tasks while manually testing a work-in-progress app during early and active development. So far, I love that the deno watcher can react to source file changes! One of my goals would be to automate mundane tasks in order to observe feedback faster as I: learn deno, prototype ideas, experiment with browser APIs / paradigms (e.g. saving state on document hide via session storage), etc. Specifically, I would love the ability to programmatically reload a specific Chrome tab (e.g. via puppeteer), anytime that I save code changes. So far (after about a couple days of wrestling with Bash scripting), I've got a crude working prototype for this use case. Yet, I can't help but wonder if there was a simpler way all along that I haven't discovered yet in my research attempts. I would absolutely love an idea like deno run -A --watch --watcher-debugging-port=<port> --watch-autostart=<true|false> app.ts that could maybe expose a WebSocket server on the debugging port for clients to listen to watcher events, perhaps including but not limited to: * process initial start * process initial fail * process about to restart * process successfully restarted * process failed to restart * end of watching (e.g. upon a kill -TERM) I imagine such an idea (or something similarly functional) would be simpler to understand, less brittle to maintain, and more functionally capable than a Bash 3.2 script that extracts a ws://<...> URL and coordinates between background subprocesses for <...>/MacOS/Google\ Chrome and deno run --watch <script-to-connect-puppeteer-and-serve-app-and-reload-page.ts>. For those curious enough, here is what my current Bash script does: * Makes an auxiliary fifo pipe * Spawns a new Chrome instance * as a background child process * using a hard-coded executable path (already installed on my computer) * with a --remote-debugging-port=0 and fixed value for --user-data-dir * redirecting std{out,err} into the fifo pipe * Awaits for Chrome's debug URL to appear * via blocking reads on the the fifo pipe * by pattern matching for a line that starts with DevTools listening on * and additionally extracting the websocket URL's hostport * Spawns my custom puppeteer script with Chrome's debug URL as an argument * as a background process * via a deno run --watch with very granular permissions like * --allow-env=PUPPETEER_PRODUCT * --allow-net=:8000,"${ws_hostport?}" * that roughly does Deno.serve({ onListen(...) { ... puppeteer.connect({ browserWSEndpoint, ... }) ... /* find or create browser page; goto App URL */ } }) * Sends a kill -TERM to the puppeteer script if Chrome process finishes earlier than usual (e.g. if I manually quit Chrome)
7 Replies
Deno AI Helper
Deno AI Helper•11mo ago
Based on the documentation, there isn't a built-in way to achieve exactly what you're looking for. The deno run --watch command does watch for file changes and restarts the process, but it doesn't provide hooks or events that you can listen to during the restart process. The documentation does mention that the file watcher automatically discovers files to watch based on the files contained in the module graph for the deno run subcommand. It also mentions that the --watch flag for deno run accepts an optional list of external files that should also be watched for changes. However, it doesn't provide a way to hook into the restart process or listen for events. As for the idea of exposing a WebSocket server on a debugging port for clients to listen to watcher events, this is not mentioned in the documentation. The documentation does mention that Deno supports the V8 Inspector Protocol used by Chrome, Edge, and Node.js, which allows you to debug Deno programs using Chrome DevTools or other clients that support the protocol. However, this is more about debugging your code and doesn't provide a way to listen for watcher events. In conclusion, while Deno provides some powerful features for watching files and debugging your code, it doesn't currently provide a built-in way to achieve exactly what you're looking for based on the documentation. Your current approach of using a Bash script might be the best solution for now. However, Deno is a rapidly evolving platform, so it's possible that more advanced features for interacting with the file watcher could be added in the future. 🦕
NDH
NDH•11mo ago
I have an injectable Hot-Refresh that uses SSE rather than WS. https://gist.github.com/nhrones/85080a9cc993bf3629ceaf5bb8426a30
Gist
Hot Browser Refresh
Hot Browser Refresh. GitHub Gist: instantly share code, notes, and snippets.
Ohm
Ohm•11mo ago
Wow, this is a totally different approach and neat approach! If I understand it well enough, the major advantage over puppeteer (for a use case as simple as programmatically reloading-on-demand) is that it is more inherently supported across more browsers. Additionally, it appears less reliant on external libraries (namely: avoiding a dependency on puppeteer inside an instrumented helper script). (Though, I imagine this is not considered a dealbreaker for scenarios like manual testing.) I imagine the optimization to avoid reloads on pure CSS can be instrumented with puppeteer with similar complexity. I don’t know enough about SSE, so I don’t know how robust it will be when I allow the server app process to restart after saving server source code changes. I have not tested it out myself, so I can only speculate behavior in the worst case (upon glancing at the EventSource spec). The only situation I speculate being weary about is when I might accidentally save server code that compiles but fails to reach a healthy server, in which case I imagine that the browser would retry the connection for a couple times before deeming the server as unhealthy and closing the EventSource until the developer manually refreshes the browser.
NDH
NDH•11mo ago
This deals only with client code! We watch for change to files in /src/. These are client code only. All code in /src is bundled to a bundle.js file that. The change (bundle.js) will stream a message to the script in the browser that forces the refresh. Changes to the server/service is not watched.
Ohm
Ohm•11mo ago
Gotcha. Thanks for sharing this paradigm! I’ll keep an open mind about this idea for when my server development code no longer changes heavily and whenever my focus transitions primarily to cross-browser client code
Ohm
Ohm•11mo ago
After a week of learning more things, I've learned that development mode for Deno fresh does auto-refresh the web page ([1]). I've observed that it does so via SSE and esbuild as well. It also takes advantage of things like ETags (and If-None-Match header) for static resources. Neat! I also noticed that whenever the local development server becomes unhealthy (e.g. upon quitting the deno task start process, or upon saving during incomplete / typoed / buggy code), the EventSource within Chrome seems to indefinitely retry the connection every 100ms or so. That goes against what I initially (and likely mistakenly) understood from the processing model of SSE ([2]). Not a big deal for local development, and perhaps something I'll depend on for now! [1] https://fresh.deno.dev/docs/getting-started/running-locally#:~:text=updates%20automatically [2] https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model
Running locally | Fresh docs
To start a Fresh project, just run deno task start. This will start the project with default permission flags, in watch mode.
NDH
NDH•11mo ago
Chrome seems to indefinitely retry the connection every 100ms or so.
Chrome seems to indefinitely retry the connection every 100ms or so.
I think you'll find that in a spec somewhere. EventSource, unlike WS, is self-healing. The browser will continually try to reconnect.