Since ClojureScript relies on Google Closure compiler to “get down” to JavaScript, in order to take advantage of an “advanced” compilation, external JavaScript libraries have to follow certain Google Closure standards.
A Google Closure compiler introduces the concept of the “extern”: a symbol that is defined in code external to the code processed by the compiler. This is needed to exclude certain JS vars and functions that do not follow the standard that the g-closure advanced compilation relies on.
Many JS libraries do not follow g-closure standards, and there is a closure-compiler repository with some pre-built externs for JQuery, Jasmine, AngularJS and others. However there are about X thousand more JS libraries that could be useful while writing apps with ClojureScript.
While there is a way to manually go through all the ClojureScript code, find “external” JS vars/functions and write externs for them, there is a much nicer alternative written by Chris Houser in a gist: “Externs for ClojureScript” that creates a “DummyExternClass” and attaches all the vars and functions that are not part of (not recognized by) “core.cljs”.
Here is an example of creating externs for an arbitrary ClojureScript file that uses a nice chap timeline JS library:
user=> (print (externs-for-cljs ".../cljs/timeline.cljs")) var document={}; var links.Timeline={}; var links.events={}; var DummyExternClass={}; DummyExternClass.toString=function(){}; DummyExternClass.substring=function(){}; DummyExternClass.getVisibleChartRange=function(){}; DummyExternClass.getTime=function(){}; DummyExternClass.start=function(){}; DummyExternClass.end=function(){}; DummyExternClass.getSelection=function(){}; DummyExternClass.row=function(){}; DummyExternClass.setSelection=function(){}; DummyExternClass.setCustomTime=function(){}; DummyExternClass.setVisibleChartToDate=function(){}; DummyExternClass.getElementById=function(){}; DummyExternClass.addListener=function(){}; DummyExternClass.draw=function(){}; |
The original file itself is not important, the output is. “externs-for-cljs” treated a couple of namespaced functions as vars, but it is a easy fix:
var links={}; links.Timeline=function(){}; links.events=function(){}; |
At this point the whole output can be saved as “timeline-externs.js”, and pointed to by “lein-cljsbuild”:
span style="color: #0086b3;"> :cljsbuild {:builds [{:source-paths [...], :compiler {:source-map ..., :output-to ..., :externs ["resources/public/js/externs/timeline-externs.js"] :optimizations :advanced}}]} |
ClojureScript files based on other JS libraries that are not in a closure compiler repo: e.g. Twitter Bootstrap, Raphael and others can be “extern”ed the same way in order to take advantage of g-closure advanced compilation.
Interesting bit here that is not related to externs, but is to an advanced compilation is a “:source-map” attribute which is a way to map a combined/minified file back to an unbuilt state. It generates a source map which holds information about original JS files. When you query a certain line and column number in the (advanced) generated JavaScript you can do a lookup in the source map which returns the original location. Very handy to debug “:advanced” compiled ClojureScript.
For more info: