Integrating third party JavaScript libraries not written with Google Closure Compiler in mind continues to both be a source of error for users when going to production, and significant vigilance and effort for the the broader community (CLJSJS libraries must provide up-to-date and accurate externs).
In truth writing externs is far simpler than most users imagine. You only need externs for the parts of the library you actually intend to use from ClojureScript. However this isn't so easy to determine from Closure's own documentation. Still in the process of writing your code it's easy to miss a case. In production you will see the much dreaded error that some mangled name does not exist. Fortunately it's possible to enable some compiler flags :pretty-print true :pseudo-names true
to generate an advanced build with human readable names. However debugging missing externs means compiling your production build for each missed case. So much time wasted for such simple mistakes damages our sense of productivity.
If we squint a bit perhaps we can see how this issue isn't quite so different from the one of reflection in Clojure JVM. When writing high performance code it's easy to miss a needed type hint and get orders of magnitude worse performance. In order to help the user easily locate the issue, Clojure has long had a per-file level dynamic var *warn-on-reflection*
which allows users to easily locate forms where the compiler could not resolve the type.
(set! *warn-on-reflection* true)
(defn foo [x]
(.indexOf x "bar"))
;; Reflection warning - call to method indexOf can't be resolved (target class is unknown).
All you need to do is add a type hint and the warning goes away and the compiler will generate optimal bytecode:
(set! *warn-on-reflection* true)
(defn foo [^String x]
(.indexOf x "bar"))
But this is precisely what interop into third party JavaScript looks like, only the performance concern is irrelevant. Consider if we had a file level warning for interop calls where we can't resolve the type.
(set! *warn-on-infer* true)
(defn foo [c]
(.render c))
;; WARNING: Cannot infer target type for (. c render) at line ...
Again all we need to do is add a type hint and the warning goes away:
(set! *warn-on-infer* true)
(defn foo [^js/React.Component c]
(.render c))
In this case we didn't make the code 100X faster, instead we now have enough information to automatically generate the extern for you:
var React;
React.Component;
React.Component.prototype.render;
This isn't just a thought experiment. As of today we have experimental support for the above in ClojureScript master. Simply add a new compiler option :infer-externs true
to your compiler config. Now when you build your project you will get a new file in your :output-dir
named inferred_externs.js
. When you do an advanced build, this externs file will be used.
Please give it a spin and report issues and ideas for further enhancement!
I'd like to thank Maria Geller in particular, this feature is based on her Google Summer of Code work.
@agzam until a new ClojureScript build is released I think you have to compile it yourself from the source. It worked for me and was seamless. See https://github.com/clojure/clojurescript/wiki/Building-the-compiler
In case somebody want's to use this with figwheel, be sure to use the latest version of lein-figwheel, see bhauman/lein-figwheel#507