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"))
(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
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.