Parameterizing ClojureScript Builds
Just like with most server side software we often want to make minor changes to the behaviour of the code depending on the environment it's run in. This post highlights language and compiler features of ClojureScript making parameterized builds easy peasy.
On servers environment variables are a go-to solution to set things like a database URI. In ClojureScript we don't have access to those. You can work around that with macros and emit code based on environment variables but this requires additional code and separate tools.
With ClojureScript 1.7.48 (Update: There was a
bug in 1.7.48 goog-define
. Use 1.7.107 instead.) a
new macro goog-define
has been added which allows
build customization at compile time using plain compiler options.
Let's walk through an example:
(ns your.app)
(goog-define api-uri "http://your.api.com")
goog-define
emits code that looks something like
this:
/** @define {string} */
goog.define("your.app.api_uri","http://your.api.com");
The goog.define
function from Closure's standard
library plus the JSDoc @define
annotation tell the
Closure compiler that your.app.api_uri
is a constant
that can be overridden at compile time. To do so you just need to
pass the appropriate :closure-defines
compiler
option:
:closure-defines {'your.app/api-uri "http://your-dev.api.com"}
Note: When using Leinigen quoting is implicit so there is no quote necessary before the symbol.
Note: Sometimes for debugging you may want to
pass the Closure define as a string. If you decide to do so make
sure it matches the string in the goog.define
call in
your emitted Javascript (i.e. account for name mangling).
Under the hood
When compiling with :advanced
optimizations the
Closure compiler will automatically replace all occurrences of
your defined constants with their respective values. If this leads
to unreachable branches in your code they will be removed as
dead code
by the Closure compiler. Very handy to elide things like logging!
Without any optimizations (:none
)
goog.define
makes sure the right value is used. There
are two global variables it takes into account for that:
CLOSURE_UNCOMPILED_DEFINES
and
CLOSURE_DEFINES
. When you override the default value
using :closure-defines
the ClojureScript compiler
prepends CLOSURE_UNCOMPILED_DEFINES
with your
overridden define to your build causing
goog.define
to use the value in there instead of the
default value you defined in your source files.
For details see the source of goog.define.