anmonteiro / lumo

Fast, cross-platform, standalone ClojureScript environment
Eclipse Public License 1.0
1.88k stars 85 forks source link

How about watching files and reload namespaces #11

Closed tiye closed 7 years ago

tiye commented 7 years ago

Since lumo now utilizes Node.js API, we may do more than Planck. One things I tried but felt clumsy is that I want to write small Node.js apps in ClojureScript so I tried fighweel. But it requires quite some code to config figwheel, what's more, when I'm migrated to Boot, it's even more configs.

How about such a feature in lumo? Like detect file changes, and reload namespaces, and call a on-jsload function. It's like we do (require 'x.y :reload) (x.core/on-jsload) manually but now we do it with lumo.

I guess anyway we have to compile ClojureScript in :advanced mode to make performant. No mention to use it in production.

anmonteiro commented 7 years ago

This is a cool idea, but I don't think it belongs in Lumo.

Happy to see this done as a library and to provide the necessary API hooks for it to work.

tiye commented 7 years ago

Do you have any clue on how to build this kind of thing with lumo APIs now?

anmonteiro commented 7 years ago

I haven't given it any thought yet, so no :-)

arichiardi commented 7 years ago

I was exactly thinking about this yesterday.

In boot, the watch task triggers the re-trigger (:smile:) of tasks. Therefore if we want to accomplish server side reloading somebody would need to build a simple task that "speaks with" lumo at every re-trigger, sending the changed namespaces (source of inspiration) and then letting lumo do the require + on-jsload.

Typically this is achieved with a websocket server (both boot-reload and lein-figwheel) do this.

I agree that all this should be in a separate library. It would actually be great to package common code (the websocket lib and the comm protocol and maybe the browser client) in one library so that it can be used in other projects as well.

tiye commented 7 years ago

Suppose I have to files,

main.cljs

(ns main
  (:require [lib :as lib]))

(defn doit []
  (println lib/a))

lib.cljs

(ns lib)

(def a 2)

And I can do this:

cljs.user=> (require '[main :as main])
WARNING: main is a single segment namespace at line 2 main.cljs
WARNING: lib is a single segment namespace at line 2 lib.cljs
nil
cljs.user=> (main/doit)
1
nil

Then I update lib.cljs and reload it:

cljs.user=> (require '[lib :as lib] :reload)
WARNING: lib is a single segment namespace at line 2 lib.cljs
nil
cljs.user=> (main/doit)
2
nil

So it looks not hard to watch and get the changed path, then there are expressions left in the repl.

If we can connect to the same process(the REPL) and send a piece of code, it would work. And that watcher don't even need to be written in ClojureScript.

Do you think TCP Socket REPL is capable of doing this?

jmfirth commented 7 years ago

This kind of intrigued me so I wrote a very naive, but functional enough to show, method of doing this in lumo using the socket REPL. It basically works as inf-clojure-eval-buffer would, sending the changed file contents to the socket REPL for evaluation.

https://github.com/jmfirth/lumo-watch/blob/master/watch.cljs

About halfway through implementation it was working in some capacity and it was very interesting/weird developing this with itself once initially evaluated. I'm not sure if I intend to try and take this any further, but it was a fun diversion this morning.

tiye commented 7 years ago

@jmfirth looks interesting. I'm not familiar with Socket REPL, do you see any guide out there?

For the watcher I would recommend https://github.com/shama/gaze for performance.

Seems in your code namespaces are not handled manually, does it reload?

tiye commented 7 years ago

I got a experimental version. I tried but it appears to be buggy. https://github.com/Cumulo/cumulo-workflow/blob/master/server/polyfill/workflow_server/watcher.cljs

tiye commented 7 years ago

Thanks to guide from @pesterhazy , I figured out how to use socket REPL in this case.

In short, it's as simple as sending TCP requests:

(def net (js/require "net"))
(def client (.createConnection net (clj->js {:port 6000})))
(.on client "data" (fn [chunk]
  (.log js/console (.toString chunk))))

; in real programs, need to make sure connection open first
(.write client (str "(require '" ns-path " :reload)" \newline))
(.write client "(require '[workflow-server.main :as main])\n")
(.write client "(main/on-jsload!)\n")

My entire demo is in https://github.com/Cumulo/cumulo-workflow/blob/master/server/tasks/watcher.cljs

It's OK for me to use an external script to do it. Closing...