How to properly setup shadow-cljs for hot reload? [closed] - clojure

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I've been trying to get shadow-cljs hot reload to work but I haven't been able to, I've tried multiple settings in my project.clj file but none have worked. This is what my project.clj looks like:
:shadow-cljs {:nrepl {:port 8777}
:builds {:app {:target :browser
:output-dir "resources/public/js/compiled"
:asset-path "/js/compiled"
:modules {:app {:init-fn my-app.core/init
:preloads [devtools.preload]}}
:devtools {:http-root "resources/public"
:http-port 8080
:http-handler my-app.handler/dev-handler
}}}}
:aliases {"watch" ["with-profile" "dev" "do"
["shadow" "watch" "app"]]}
:profiles
{:dev
{:dependencies [[binaryage/devtools "1.0.2"]]
:source-paths ["dev"]} ;; <- this is the default from the template pointing to "dev", I've tried chaning it multiple times.
}
When I run lein watch I understand that it's just running shadow-cljs watch app which should run my "app" build. This is what my dev-handler looks like (referenced in :http-handler):
(def dev-handler (-> api-routes
wrap-params
wrap-json-response
wrap-keyword-params
wrap-json-params
wrap-multipart-params
wrap-reload
(wrap-resource "public")
(utils/cors-wrapper utils/cors-policy)
(utils/wrap-content-type-security)
(wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false))))
I've tried changing the source-paths in my :dev profile to ["src"] and even tried including all inner folders as in ["src/clj" "src/cljs" "src/cljc"] with no success. I've even tried creating a new app from scratch with lein new re-frame making no changes whatsoever and running lein watch, code compiles and everything seems fine but whenever I change something in a cljs file (views.cljs for example) nothing is re-rendered/reloaded, I've gone to localhost:9630 which gives you a shadow-cljs dashboard listing your builds and clicked on the "force compile" button and see a clojurescript animation in my app's page but no re-rendering/reloading again. I notice that when shadow-cljs finishes compiling, there are 0 files compiled, is it getting the source-paths config from somewhere else? This is what it looks like when I'm trying to test it out:
EDIT: Maybe it's worth saying that I'm running ubuntu with xfce terminal and emacs from WSL2 within MobaXterm, could it have something to do with the code/app not hot reloading?

Everything seems to be running smoothly and as intended. It might be that the only thing you are missing is a lifecycle callback to trigger the actual re-render of your page.
See https://shadow-cljs.github.io/docs/UsersGuide.html#_hot_code_reload
In most re-frame/reagent apps that would be the function that calls reagent.dom/render (or reagent.core in case you are on old version).
I also wrote more about how this all works here.
... is it getting the source-paths config from somewhere else?
shadow-cljs looks at the JVM classpath however that is constructed on startup. You appear to be using lein so only project.clj will matter here and the classpath is constructed by adding all :source-paths, :resource-paths and so on. So if you have :source-paths ["src/clj" "src/cljs" "src/cljc"] that will be enough for it to be picked up, assuming of course the source files you are working with are actually in those dirs.
Can't comment on your setup otherwise. I used it fine with WSL2 but there are known issues if running inside containers (eg. docker) so I wouldn't rule out something being funky here that prevents shadow-cljs from actually "watching" the files.

Related

lein uberjar taking forever

I want to create an uberjar of a leiningen app. My config is:
:uberjar {:omit-source true
:aot :all
:uberjar-name "myapp.jar"
:source-paths ["env/prod/clj" ]
:resource-paths ["env/prod/resources"]}
But upon doing lein uberjar, I find the the files in the project are being compiled, but the compilation is stuck on the file that contains most of the code, for ten minutes and counting. This file doesn't contain more than 140 lines.
TL;DR: never def side-effects
As stated in the comments:
... I just figured that this line: (defonce server (http/start-server server-handler {:port 8982})) is causing the hang.
Don't put stuff like that at top-level.
defonce only means it will not be re-def-ed once it's there (so in this case
it would prevent some "port already in use" error on reloading.
Ways out of that dilemma
Write a function, that starts this server. Then call that from your main. For
development you can run that function from the REPL or you can sprinkle some
reload/restart logic in your user-ns.
Another option could be using delay: it will only execute the code once it gets derefed.
The more "binding of resources" you have to deal with, the more some systematic
approach will give your application a better structure. E.g. take a look at:
weavejester/integrant
stuartsierra/component
tolitius/mount
So why is putting blocking things or side-effects in a def problematic?
The way the Clojure compiler works, is by actually "running" the code. So compile basically is:
enable generation byte code and write it out as .class files
load the namespace and "run" it
This means, that at compile time, the top level side-effects are executed.
Therefor blocking operations in a def, will block compilation (which is quite
obvious), or your CI server will fail to compile, because it can not connect to
the database etc.
A great explanation of how the code generation in Clojure works:
What Are All These Class Files Even About? And Other Stories - Gary Fredericks

clojure.tools/namespace refresh fails with "No namespace: foo"

I'm using tools.namespace to provide smart reloading of namespaces on the REPL. However, when calling refresh or refresh-all, it throws an error.
user=> (require '[clojure.tools.namespace.repl :as tn])
user=> (tn/refresh)
:reloading (ep31.common ep31.routes ep31.config ep31.application user ep31.common-test ep31.example-test)
:error-while-loading user
java.lang.Exception: No namespace: ep31.config, compiling:(user.clj:1:1)
And it seems to end up in this weird state where (require ep31.config) works without an error, but afterwards the namespace isn't actually defined.
I kind of figured this out, this seems to be a combination of circumstances
there were AOT compiled classes left in target/classes from doing lein uberjar previously
tools.namespace doesn't function correctly when loaded namespaces are AOT compiled
target/classes is by default on the classpath
So long story short, if you did a jar/uberjar build before, then remove target/ and things should start working again.
The question I haven't been able to solve yet is why target/classes is on the classpath to begin with. I'm suspecting it's being added by Leiningen, but haven't found yet where or why it's happening.
I learned this the hard way, documentation for :target-path says (https://github.com/technomancy/leiningen/blob/master/sample.project.clj#L309-L313):
;; All generated files will be placed in :target-path. In order to avoid
;; cross-profile contamination (for instance, uberjar classes interfering
;; with development), it's recommended to include %s in in your custom
;; :target-path, which will splice in names of the currently active profiles.
:target-path "target/%s/"
I guess there has to be legacy reasons that :target-path "target/%s/" isn't the default.

What's the correct approach to debugging a pedestal.io app?

I'm currently trying to reimplement the todo example app to understand how it works and I'm getting an error when I load the page. I'm not certain how to go from here. What concerns me is the error appears to be in cljs.core.
todo-app.simulated.services.receive_messages = (function receive_messages(app){
return io.pedestal.app.protocols.put_message.call(null,(new cljs.core.Keyword("\uFDD0:input")).call(null,app),cljs.core.PersistentArrayMap.fromArray([io.pedestal.app.messages.type,"\uFDD0:create-todo",io.pedestal.app.messages.topic,cljs.core.PersistentVector.fromArray(["\uFDD0:todo"], true)], true));
});
The exception message is:
Uncaught TypeError: Object function (meta,cnt,arr,__hash){
this.meta = meta;
this.cnt = cnt;
this.arr = arr;
this.__hash = __hash;
this.cljs$lang$protocol_mask$partition1$ = 4;
this.cljs$lang$protocol_mask$partition0$ = 16123663;
} has no method 'fromArray'
And my dependencies are:
[[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-1820"]
[domina "1.0.1"]
[ch.qos.logback/logback-classic "1.0.7" :exclusions [org.slf4j/slf4j-api]]
[io.pedestal/pedestal.app "0.1.9"]
[io.pedestal/pedestal.app-tools "0.1.9"]]
Any help or insight would be appreciated!
I was seeing this error too, and it seemed like it came out of nowhere. Clearing the out/ dir (:target-path in your project.clj) fixed it for me. Based off that, I think there was some disconnect in the cljs compilation process and/or pedestal.
This issue looks similar and the fix was similar, so I assume it's a cljs build problem.
I don't have much to offer regarding pedestal debugging in general, but if I see an error that appears to be in a core library, I start from the assumption that something is wrong on my end. :)
EDIT
A little more info, it's recommended to delete the out\ dir every time you upgrade ClojureScript or Pedestal.
As bostonou suggested, the best approach is deleting the out directory. My current approach is to use lein-cljsbuild, I personally do this by adding it to my user profile.
You can do so by calling nano ~/.lein/profiles.clj
Mine currently looks like:
{:user {:plugins [[lein-difftest "2.0.0"]
[lein-marginalia "0.7.1"]
[lein-pprint "1.1.1"]
[lein-swank "1.4.4"]
[lein-catnip "0.5.1"]
[environ/environ.lein "0.3.0"]
[lein-cljsbuild "0.3.2"]]
:hooks [environ.leiningen.hooks]}}
You can now automatically build cljs files by calling lein-cljsbuild once inside the project folder. Calling lein-cljsbuild auto ensures that when the source files are edited then they are automatically compiled.
I also currently add :hooks [leiningen.cljsbuild] to my project.clj so that calling lein clean will also remove files built by lein-cljsbuild.

How do I start many ring subprojects with a single lein task?

I have a project with three subprojects like so (using lein-sub):
(defproject trident "0.1.0"
...
:sub
["admin"
"api"
"site"])
Each has a ring handler inside, something like this:
(defproject trident-api "0.1.0-SNAPSHOT"
...
:ring {:handler trident.api.core/handler
:servlet-name "trident-api"
:init trident.api.core/init
:port 33333})
Right now I go into each and type:
> lein ring server
This obviously gets tiresome. I also tried
> lein sub ring server
Which seems like it should work, but it only starts the first one and the process joins it! Possibly there is a way around that? It seems like this would be the simplest approach.
In lieu of that, I am now writing a leiningen task to do this for me, but am running into some issues. I can get the project file for each easily enough using leiningen.core.project/read, but from here, how do I start each ring server in a way that honors all of the dependencies and other settings from their respective project.clj files?
Any ideas?
This is just a suggestion, as I am not able to verify if this will work right now.
There is a var in leiningen.core.project that identifies default values. Maybe you could write a plugin (or fork lein-sub?) and have it override these values for the sub project? Then you can create a plugin that iterates over each sub project while applying a given task to each one.
For example, the defaults declares the source path like so:
:source-paths ["src"]
You could then override it with the following for each sub project:
:source-paths ["sub-project/src"]
Do that with all the pertinent defaults, and it might just work.
There might be a way to do this with Leiningen 2's profiles, but I'm not sure. I imagine if you create a profile for each sub project in the parent project, you could easily merge the profile when invoking the task on the respective sub project.
I ended up building a metaserver to start all three jetty instances at once. Code is here:
https://github.com/antler/lein-caribou/blob/master/src/leiningen/caribou/server.clj

Enclojure REPL can't find dependent clj file on Load

I have a problem with the Enclojure REPL and using clojure modules from it. The Load/Change REPL to file/ns works fine with an isolated clojure file, but breaks with a file which has references to another clojure file which I try to use from my project.
Here are the exact steps:
Create a new project.
Create a clojure module foobar.clj (namespace com.acme.foobar)
Define a function which returns a value in foobar.clj:
(ns com.acme.foobar
(:use com.acme.othermodule))
(defn myfunc1 []
"a")
Open a Netbeans IDE REPL
From foobar.clj's context menu select:
Change REPL to file/ns
Load
From REPL call the (myfunc1) function. This works just fine:
com.acme.foobar=> (myfunc1)
"a"
The problems start when when I try to refer to other files from foobar. Here's what I do:
Create a new clojure module othermodule.clj
(ns com.acme.othermodule)
(defn fromothermodule []
"b")
Now try to call this from foobar.clj:
(defn myfunc2 []
(fromothermodule))
From othermodule.clj's context menu I select:
Change REPL to file/ns
Load
To make the REPL realize that there is new module it should be able to run.
I do same things things to foobar.clj which now refers to othermodule.clj, but I get:
CompilerException java.io.FileNotFoundException: Could not locate com/acme/othermodule__init.class or com/acme/othermodule.clj on classpath: (NO_SOURCE_FILE:50)
com.acme.foobar=>
This error message comes from both "Change REPL to file/ns" and "Load"
What am I missing? Should I do some other tricks to make this happen? Even the desperate measure of Run->Clean and Build the main project doesn't help (that would of course make the REPL business pretty painful anyway).
I am using NetBeans 6.7.1 and enclojure-plugin-2009-11-3.nbm.
Got the right solution from Eric Thorsen in the google group:
There are three ways to create the REPL from window-menu. Don't use any of those, instead right
click on the Project and "Start Project REPL". Now the paths are set up accordingly.
My first recommendation is to move from NetBeans/Enclojure to IDEA/La Clojure. JetBrains recently created an open source version of their IDE, and the Clojure plugin works fine in it. Since discovering this, I ditched NetBeans and Enclojure. I find La Clojure a pleasure to work in, but of course your mileage may vary.
Back from when I did this in NetBeans, I seem to remember Enclojure source code resides in a subdirectory called "lib". I think I solved a similar problem by fiddling with directory prefixes on the name of the file to load. Probably something like "../lib/YourName". I managed this by trial and error, so I can't relate the exact rules and syntax.
Two things that might help:
You can run something like (println (System/getProperty "java.user.dir")) to find out where Clojure thinks it's executing from.
You could start, as I did, with using an absolute path until you find your way to the correct directory name. Something like "/home/carl/NetbeansProjects/MyProject/lib/myfile.clj" .