thheller / shadow-cljs

ClojureScript compilation made easy
https://github.com/thheller/shadow-cljs
Eclipse Public License 1.0
2.23k stars 173 forks source link

defonce overwritten in advanced mode #1185

Open danjohansson opened 3 weeks ago

danjohansson commented 3 weeks ago

The following code gives the printout x: 2 Expected printout x: 1

(ns test)

(defn run-defonce [v] (defonce x v))
(run-defonce 1)
(run-defonce 2)

(println "x: " x)

I have tried the same code in the clojure clojurescript compiler, with the advanced mode setting turned on, without being able to reproduce.

thheller commented 3 weeks ago

It is correct that shadow-cljs treats defonce just like def for release builds, as generally defonce only exists as a REPL aid so that reloading a namespace does not overwrite state you might want to keep.

There is currently no flag to turn this off. I could add one, but would rather get you away from doing something you are not supposed to be doing. def, defonce, defn are all supposed to be top level forms. Embeeding a defonce inside a defn is nightmare fuel.

Why are you doing this in the first place?

danjohansson commented 3 weeks ago

Ok thanks for clarifying. I was surprised that it worked the way it did and not according to the Clojure documentation. I guess this would be the main argument for changing this.

We use it during initialization as a way of making sure some code only run once. Knowing that the pattern is problematic in general, I thought this would be safe. Guess I was wrong! :smile:

thheller commented 3 weeks ago

well, if you def something that only runs once as a namespace is only ever loaded once in release builds.

So, (defonce x (only-ever-runs-once 1)) works just fine.