Clojurescript works in dev but not production

clojurescript
#1

I’m working on an app that uses a d3js visualization on one page. When I am in dev mode, running figwheel, it works beautifully. But when I do lein uberjar that particular page won’t load, with by browser whining like “TypeError: a.En is not a function” and pointing at the compiled app.js file. Does this ring bells for anyone, where we’re working while in our figwheel/repl dev cycle, but not when compiled?

#2

When ClojureScript is compiled with advanced optimizations, functions will be renamed to short names like the one in your error. When running locally, optimizations are typically off.

This can cause errors when you interface with third party code. If you’re trying to call out to functions in d3, but use d3 with “just a link in the HTML”, you might get a rename that breaks your code.

  • Are you using d3.js from CLJSJS (which should handle the issues above) or some other way?
  • Are you compiling with advanced optimizations?

I don’t have a bunch of experience with this, but you could do a lot worse than starting with the Shadow CLJS Manual chapters JavaScript Integration and Generating Production Code.

1 Like
#3

Your answer sounds very plausible. Strangeness: when I turn :optimization :none I end up with ReferenceError: goog is not defined and the whole app fails, which really surprises me. When I turn :optimization :advanced I am back to the above A.fn error (only on the network graph page).

Regarding library versions, this is actually using a .js file directly since it isnt’ available as a webjar: https://github.com/vasturiano/3d-force-graph.

#4

I got the whole thing to work by setting :optimizations :whitespace. I understand that :advanced probably has issues with the 3rd party library I’m including; but why would :none give me the ‘goog not defined’ error?

#5

Glad you got it fixed! Why :optimizations :whitespace would fix that is beyond me. :optimizations :simple may also work.

#6

Shadow-cljs is really good with npm libs,

Using http://cljsjs.github.io d3 lib might also work if you dont want to use shadow.

1 Like
#7

Actually, I tried :simple and it did the same variable-name munging that broke it with :advanced…

#8

If you use d3.js directly (without providing an extern or without cljsjs) I’m sure you cannot use advanced compilation. Everything gets renamed, and external hard code gets renamed in a way that breaks the call sites.
If you really want to use d3, since it’s been split into several small packages lately iirc, you could very well grab just the small files from script tags and to call the functions use ((goog.object/getValueByKeys js/window "d3" "some" function") arg1 arg2), and still be able to use advanced compilation.

#9

Just add externs … it is not that hard. Escpecially if you use ^js annotations and just let the compiler write them for you.

https://clojurescript.org/guides/externs#externs-inference

shadow-cljs automates things a bit more so it is even easier but it works just fine with the “other” CLJS tools as well.

2 Likes
#10

You may want to try this library https://github.com/myguidingstar/fence

1 Like
#11

Fence looks terrific! However, it causes compilation to fail as soon as it’s put in my dependencies. I’ve filed an issue, but since the project hasn’t been updated since 2014, I’m not holding my breath.

#12

Haha. Just noticed that @myguidingstar is the creator of fence. Maybe I will hold my breath.

#13

I haven’t looked at it, but have you tried :exclusions in your dependencies to exclude fence's own dependencies?

#14

I’ve not done that before; I assume a lein deps tree comparison is the right tool to figure out what I would put there?

#15

Finished this off by using an externs file, which I had dev mode generate for me and then stuck into an externs location the build knew to look for. I’ve never compared :advanced compilation before, but found it very gratifying:

Compiled, :whitespace optimization: 4.48 mb
Compiled, :advanced optimization: 910.96 kb

3 Likes
#16

I guess not in this case because lein deps would recommend a safe choice which is using the oldest clojure verion (fence use 1.6.0) while you can pick newer version because fence doesn’t use anything specific.
Anyway, congrats for the first succeeded production build :wink:

#17

I think there’s a way on fence to declare the dependency on clojure to not be transitive for your consumers.

That’s generally the prefered way for libraries I feel.

1 Like