Getting Closure advanced compilation working with complex js dependencies

clojurescript

#1

We’re working on a more-or-less complex ClojureScript (re-frame) codebase here:

  • 14k sloc
  • multiple js dependencies, partially via cljsjs (e.g. three.js), but also as “raw” :foreign-libs

Now the interesting question arises how to get advanced compilation working. This may sound like a trivial thing at first, but at least I cannot manage to solve it.

Maybe I’m actually looking at this the wrong way, because I already got my app working (no js errors at runtime) but something in the three.js logic seems to be broken (no images are being rendered, no error message).

Here are possible approaches with their respective problems:

  • Generate externs with the js externs generator
    • Not viable, fails with some of the dependencies
  • Hand-write externs
    • Not viable (in the short term), we’ve got lots of interop code and manually going through all source files and translating that to an externs file is tedious and error-prone. May become viable if all other options fail.
  • Auto-generate externs via externs inference
    • First problem: Even with *warn-on-infer!* set to true at the top of all source files, I strangely get no warnings even though I haven’t annotated any types and the generated js doesn’t run. That’s on the latest ClojureScript version (1.9.946)
    • Second problem: As a workaround, I activate source maps, generate the js and check where it fails, then go back to the source and add a type annotation there (so much for a fast feedback loop…). However, now my code runs without errors but it still isn’t displaying my three.js image even though the internal three.js state seems to be fine (checked with some console.log introspection of the relevant stateful three.js objects).
  • Include dependencies as js modules
    • Does not seem to be viable for dependencies which itself have many dependencies. I tried bundling every dependencies with a custom rollup.js config as an ES6 module, but still get errors that the dependencies are missing (which may seem obvious now).
  • Include an externs file of other people who got it working
    • I found this tetris project which apparently got it working some time ago and included the externs file. Same problem, no js runtime errors but also no image rendered.

In summary, I have no clue. As said above, it may be that this is not an externs problem per se but rather that advanced compilation seems to break something in three.js internally (is that even possible? or, is the minified file from cljsjs corrupted?).

I’m glad to hear any suggestions! :slight_smile:


#2

Just to double check: you set :infer-externs true in the compiler options?


#3

Have you had a look at shadow-cljs? I am also working on a relatively complex app, with around 30 npm dependencies (including complex ones like prosemirror), and I haven’t had to think about externs at all - running shadow-cljs release just works.

However, I’m not an expert in this, so I’m not sure whether it is because of shadow-cljs, or because I always use goog.object/oops to access object properties, but so far extern inference has been a non-issue for this project and I use a lot of npm dependencies.


#4

Thanks for your replies, wow, I hadn’t expected any, haha. Sorry for my late answer as I currently don’t have internet at home (yes, that’s a thing).

Yes, but good point. Here are my figwheel compiler options. The build is labelled “min” and called with lein clsbuild once min.

{:output-to "resources/public/js/compiled/my_project.js"
 :output-dir "resources/public/js/compiled/out-min"
 :main my-project.core
 :optimizations :advanced
 :infer-externs true
 ;; the next two are for debugging missing
 ;; type annotations for extern inference
 :source-map "resources/public/js/compiled/my_project.js.map"
 :pretty-print true
 ;; this closure define is probably unnecessary as it's implicit in
 ;; advanced builds
 :closure-defines {"goog.DEBUG" false}
 :foreign-libs
 [{:file "src/js/some-lib.js"
 :file-min "src/js/some-lib.min.js"
 :provides ["npm.some-lib"]}]

That sounds very interesting. I haven’t looked at shadow-cljs at all, will have a look, thanks! :slight_smile:


#5

@olieidel FWIW, it seems you hit a bug in ClojureScript: https://dev.clojure.org/jira/browse/CLJS-2491