So far, we’ve talked mostly about basic features of the language such as syntax, semantics, and the compilation process. ClojureScript also offers compelling features at a higher structural level to facilitate code organization and sharing libraries.
In ClojureScript, as in Clojure, the highest level of code organization is namespaces, used to scope global definitions. However, despite superficial similarities, namespaces in ClojureScript are implemented completely differently than they are in Clojure because it runs in a different environment. This chapter will cover what these differences are and how to use namespaces effectively in ClojureScript.
Additionally, this chapter will describe how to create and utilize libraries. Unfortunately, one of the negative effects of ClojureScript’s reliance on the Google Closure Compiler is the fact that creating and consuming libraries is not always straightforward, particularly in light of Google Closure’s Advanced Optimizations mode.
To avoid name collisions, ClojureScript symbols and keywords have a
namespace component. Each *.cljs file has its own
namespace, and every REPL session has a current
namespace (cljs.user
by default). Whenever you define a
symbol using def
or one of its derivatives (such as
defn
), the namespace of the symbol is set to the current
namespace. Symbols with the same name in different namespaces are
completely different, and will not clash.
In addition to name disambiguation, namespaces are also ClojureScript’s unit of code dependency management. A namespace may require or use other namespaces as dependencies, and dependent namespaces are always loaded before the namespace that required them when the program is run. ClojureScript does not support circular references between namespaces.
One very important difference between namespaces in ClojureScript and Clojure is that while Clojure namespaces are first-class entities that can be dynamically created and loaded at runtime, ClojureScript namespaces are statically resolved at compile time only. You can’t create or load a namespace during program execution.
As you have already seen, if you refer to a symbol without
specifying a namespace, ClojureScript will attempt to resolve the symbol
in the current namespace. To specify a namespace, use a slash between
the namespace and the name. For example, foo.bar/hello
references a symbol named hello
in the foo.bar
namespace, which is distinct from a symbol named hello
in
any other namespace.
To specify a namespace for a source file, use the ns
special form as the first form in the file. (Unlike Clojure,
ns
is not just a macro wrapping more primitive functions
(such as load
and require
) but built in to the
language directly.) In its most basic form, declaring a namespace called
foo.bar
looks like this:
(ns foo.bar)
In ClojureScript, as in Clojure, the namespace of a file needs to
match its location on the Java classpath. In the case of ClojureScript,
this is the classpath of the compiler. Each .
(period
or full stop) in the namespace translates to a subdirectory on the
classpath. For example, the namespace foo.bar.baz
should be
in a file with the path foo/bar/baz.cljs
, relative to the
classpath. This is necessary for the ClojureScript compiler to find the
file that corresponds to a namespace.
To specify that a namespace depends on another namespace, add a
:require
form in the ns
form, containing any
number of specifications:
(ns application (:require [foo.bar :as bar] [foo.baz :as baz]))
This will force loading of the foo.bar
and
foo.baz
namespaces before application
itself
is loaded. Labeling the namespaces with the :as
clause
means that within the foo
namespace, you can reference the
symbols in foo.bar
via a shorter “alias” bar
.
In this example, the symbol foo.bar/function
can be written
bar/function
. ClojureScript, unlike Clojure, does not
permit “bare” namespaces in a :require
clause. The compiler
will throw an exception if you specify a :require
without
:as
.
You may also reference namespaces with :use
instead
of :require
, like so:
(ns application (:use [foo.bar :only [hello goodbye]]))
The :use
form also causes the specified namespace(s)
to be loaded, but differs from :require
in that, for each
of the symbols specified in the :only
vector, it
establishes a synonym in the current namespace so you can refer to
symbols in the used namespace without explicitly qualifying them. For
example, given the above ns
declaration, you can now use
the symbols hello
and goodbye
directly in the
foo
namespace to refer to foo.bar/hello
and
foo.bar/goodbye
.
Again, the use of :only
in a :use
specification is mandatory in ClojureScript, and will cause a compile
error if missing. Additionally, unlike Clojure, :require
and :use
are the only forms allowed within an
ns
declaration. Clojure’s :import
,
:refer
, etc., are not supported. Therefore, although a
valid ClojureScript ns
declaration is also always a valid
Clojure ns
declaration, the inverse is not true:
ClojureScript has much narrower requirements for validity.
The ns
form will not work in the ClojureScript REPL
(either the Rhino REPL or the browser REPL). To switch the current
namespace in the REPL, you can use the in-ns
special
form, passing it the quoted namespace:
ClojureScript:cljs.user> (in-ns 'foo.bar)
This switches the REPL’s current namespace to be
foo.bar
.
Note that in-ns
is a special tool for REPL
development, and doesn’t actually exist in ClojureScript’s standard
library. It is implemented as a special case in the REPL’s reader. As
such, it won’t work at all in *.cljs source
files, or anywhere else except in a REPL session.
Also, because ClojureScript namespaces can be defined only at
compile time, using in-ns
to switch to a namespace does
not implicitly create the namespace (as it does in Clojure). If you
specify a namespace that isn’t already loaded, the REPL will switch to
it, but almost everything you try to do will fail with a “namespace is
not defined” error.
Fundamentally, namespaces are just a tool to prevent name clashes, but they also help structure your code into logical units. They function a bit like modules or packages in other languages. Here are a few hints to use namespaces effectively:
Group similar or related functions together in the same namespace.
Try to minimize the number of dependencies
(:require
and :use
expressions) in each
namespace, except for one main namespace that ties together
everything in your application.
Never have circular dependencies between two namespaces (namespace A depends on B, which depends on A).
To really understand how ClojureScript namespaces work, it’s helpful to know something about their implementation.
When you compile a directory containing *.cljs files, the compiler emits a directory with the same structure containing compiled *.js files; each input *.cljs file has exactly one output *.js file with the same name and path.
Each emitted *.js file contains code allowing it to participate in the namespace system provided by the Google Closure Library. You can read about the Google Closure Library’s dependency management system here. Note that as a user of ClojureScript, you don’t have to worry about writing a deps.js file, or using the ClojureBuilder or DepsWriter scripts described in the Google Closure documentation. The ClojureScript compiler performs those functions internally. This is the mechanism by which ClojureScript actually resolves namespace dependencies: under the hood, ClojureScript’s namespace dependency system is that of Google Closure.
In the Google Closure Library, dependency management is handled
by two functions: goog.provide()
and
goog.require()
. The goog.provide()
function
is intended to be called once per file, and passes the namespace the
file contains. The goog.require()
function may be called
multiple times, each time with a dependency of the file. Both are
emitted directly by ClojureScript when compiling an ns
form. The mapping is very straightforward:
(ns application (:require [foo.bar :as bar] [foo.baz :as baz]))
It is compiled to:
goog.provide("application"); goog.require("foo.bar"); goog.require("foo.baz");
What happens next depends on what :optimizations
mode the ClojureScript compiler is running in.
When using :optimizations :none
, the ClojureScript
compiler will write a list of calls to
goog.addDependency()
to the specified
:output-to
file. These serve to create mappings between
namespace names, dependencies, and relative paths, and are necessary
to inform Google Closure of the location of dependencies (unlike
ClojureScript, Google Closure has no convention regarding source file
location). After you include this dependencies file in your web page,
calls to goog.require()
will dynamically add new
<script>
tags to the page using the provided
relative path.
It follows, then, that when using :optimizations
:none
you must also make the compiler output directory
(specified with :output-dir
) publicly available on a
relative path appropriate to the URL of the page. This is necessary
because the page will end up loading each of the needed
*.js files directly. You must also manually
include goog/base.js
first in your HTML, to bootstrap the
Google Closure Library, as we showed in Chapter 3.
On the other hand, when using any :optimizations
mode other than :none
, the compiler
will concatenate all the required files into one gigantic JavaScript
file, in dependency order, and write it out as the
:output-to
file. This is the only
*.js file you need to include on your HTML page,
since it contains a full copy of every dependency, even the core
Google Closure library. Therefore, in this case, it
isn’t necessary to expose the output directory
like it is with :optimizations :none
.
Because vanilla JavaScript has no built-in namespace support, it’s standard practice to use objects as a poor man’s namespace system, nesting all the variables a library uses under a single top-level object.
The ClojureScript compiler does exactly the same thing, when
compiling ClojureScript symbols to JavaScript variables. Each
dot-separated level of the namespace becomes a nested object, with the
symbol’s name as the final name. For example, the symbol my.cool.new-project/some-data
in
ClojureScript will be
my.cool.new_project.
some_data
in JavaScript.
(Hyphens in ClojureScript symbols become
underscores in JavaScript.)
For pure ClojureScript applications, this is just an implementation detail. However, it does become important when you want to call a ClojureScript function from JavaScript, since you must refer to it by its fully qualified (i.e., nested) name.
For the subsequent sections on consuming and producing libraries, one of the major challenges is working with the implications of the Google Closure Compiler’s Advanced Optimizations mode. Before moving on, it will help to have a complete idea of what Advanced mode actually does.
Advanced mode’s blessing, and its curse, is that it deeply and radically transforms your program. It does so in very beneficial ways, decreasing both code size and execution time, sometimes drastically. Advanced mode can and does:
Rename variables and functions to shorter names (sometimes called munging)
Flatten object nesting
Eliminate unused code
Create inline functions
Optimize performance based on known characteristics of JavaScript runtimes
Essentially, there is nothing that advanced mode might not do to your code, while maintaining the same semantics. Consider the following example:
function print_sum(sum) { alert('The sum is ' + sum); } print_sum(3 + 4);
Advanced mode can convert this to the following:
alert("The sum is 7");
The level of sophistication evident in this transformation speaks for itself.
But Advanced mode’s power comes at a price. Specifically, it imposes two major requirements:
It must operate on the entire program, as a whole, at once. Otherwise, it cannot safely rename variables or enact other transformations.
It only works on a subset of JavaScript. The exact restrictions can be found here. You don’t have to worry about this for ClojureScript code, since the ClojureScript compiler only emits compatible JavaScript. However, there are frequently issues with third-party libraries not designed with Google Closure in mind.
A compelling feature of ClojureScript is that it is capable of utilizing any JavaScript library. Unfortunately, because of its reliance on Google Closure dependency management and advanced mode compilation, doing so is admittedly difficult. There are several different techniques, and choosing the wrong one will result in errors, or worse, subtle bugs in your application’s behavior.
Fortunately, it is possible to import and use any library safely, as explained below. See the flowchart at the end of the section for a high-level overview of the options you have.
If the library you want to use is written in ClojureScript, your task is done. All you need to do is make sure the *.cljs files for the library are available on the classpath either as a JAR dependency or a source folder, depending on how the library is distributed.
Once the source files are on the classpath, you can
:require
or :use
them exactly the same way you
would a namespace that you wrote—in fact, from the point of view of the
system, there isn’t any difference. They will be compiled and optimized
along with your code.
You can also use libraries not originally intended for use with ClojureScript by using interop forms in your code to reference JavaScript variables (see Chapter 4).
How exactly to go about including such a library depends on the characteristics of the library in question. The procedure is different for libraries that were written with Google Closure in mind. For those that weren’t, there is another choice: compiling them in Advanced mode along with your application, or leaving them completely external.
These are files that include a call to
goog.provide()
, allowing them to participate in Google
Clojure’s dependency management system, and by extension,
ClojureScript’s. A JS file that invokes
goog.provide('x.y.z')
effectively has the namespace
x.y.z
.
Unless you work at Google, you aren’t likely to see many libraries that fit into this category, as Google Closure doesn’t have a particularly large uptake in the JavaScript community. However, if you’re using a mixed JavaScript/ClojureScript codebase, and you’d like anyone to have the freedom to modify the library, you can consider making your JavaScript compatible with Google Closure to make it easier to use with ClojureScript.
First, to use them, you must start by putting the *.js files on the classpath so they are accessible to the compiler (just like ClojureScript files).
Second, you must tell the ClojureScript compiler
where on the classpath the files are located.
Unlike ClojureScript file paths, *.js pathnames
don’t necessary have any relationship to the namespaces they provide.
To indicate this to the compiler, use the :libs
compiler
option, which is a vector of *.js pathnames
relative to the classpath. The compiler will inspect these files for
calls to goog.provide()
and handle them appropriately.
For example, to include a library in a file located at
jslib/magic.js
, the compiler options map might look
something like this:
{:output-to "resources/public/js/main.js" :optimizations :advanced :libs ["jslib/magic.js"]}
Finally, to use these libraries in your ClojureScript code,
:require
or :use
their namespaces in your
ns
declaration form. This will ensure that the library is
available. Then, use ClojureScript’s JS interop to reference the
JavaScript vars. Note that the namespace of a Google Closure library
doesn’t always match the names of the variables it declares; that’s
another ClojureScript convention that is stricter than what Google
Closure alone requires.
Because libraries built for Google Clojure should already be compatible with Advanced mode compilation, you shouldn’t need to worry about preserving variable names against munging: Advanced-mode processing will be consistent across the entire codebase, including the required libraries.
This is likely the most common type of library you might wish to use in ClojureScript: a normal, possibly popular JavaScript library that wasn’t written with ClojureScript or Google Closure in mind.
With these libraries, there is just one major choice to make: do you want to attempt to use the Google Closure Compiler’s Advanced Optimizations mode to compile the library together with your code, or include it separately on the HTML page? When you use Advanced mode, the Google Closure Compiler will perform whole-program optimization across both the library and your code. When you leave it separate, your program will still be compiled, but the external library will be loaded and run without any transformations.
As discussed above, compiling in Advanced mode has many benefits in emitted code size and runtime speed. Also, as you will see below, it’s slightly easier to use with ClojureScript. However, the fact is that most JavaScript code written without Advanced mode in mind probably doesn’t meet its rather stringent requirements.
If you do want to compile an existing library in Advanced mode, you should do a careful audit of the library’s code to make sure it meets the restrictions of Advanced mode. Be cautious: this is the one area where it is possible to go badly wrong. Sometimes incompatibilities don’t manifest in an obvious way until they’ve caused you serious trouble.
Compiling a library into your application with
Advanced mode is fairly easy, given the major
caveat that the library’s code is Advanced-mode
compatible. The process is very similar to including a library that
is built for Google Closure, the only
difference being that instead of just telling the compiler the
location of the file, it is necessary to tell it the location and
the namespace it provides using the :foreign-libs
compiler option.
:foreign-libs
must be a sequence of maps, each
containing a :file
and a :provides
key.
The :file
value is a path or URL indicating the
absolute or classpath-relative location of the file. The
:provides
key is a vector of strings naming the
namespaces that the file provides. Put together, it looks like
this:
{:output-to "resources/public/js/main.js" :optimizations :advanced :foreign-libs [{:file "http://foo.com/foobar.js" :provides ["foo.bar"]}]}
This will tell the ClojureScript compiler that when it’s
concatenating all the sources prior to running them through the
Google Closure Compiler, it should also include the source of the
given file, and it should inject a call to
goog.provide()
in the source for each of the given
namespaces.
The effect is the same as if the library had included a
goog.provide()
call on its own, and been included using
the :libs
option.
Again, this will only work if the library’s code conforms to the standards required by Advanced mode. Be especially aware that any nonconformities might not show up at compile time, although Google Closure will do its best to give warnings.
This is likely to be the most common case: you have a library you want to use, but it wasn’t built for ClojureScript or Google Clojure, and you’re not confident it is compatible with advanced-mode compilation.
The basic premise for using such a library is very simple:
just include it on your HTML page using a separate
<script>
tag, which will load it into the
JavaScript runtime environment. Since your ClojureScript program has
access to the root environment through its interop forms, you can
directly reference any of the variables the external library has
created using interop forms (such as the js/
pseudo-namespace).
Unfortunately, although this is fine for Whitespace Only or Simple Optimizations compilation, Advanced mode will cause errors without some extra work. The reason for this is variable munging. Advanced mode will potentially rename every symbol or property name mentioned in your ClojureScript code. If you’re compiling everything together with Advanced mode, the renaming will be consistent and everything will work. But if you’re only compiling your ClojureScript code and not the library, then things will be renamed with no way to match them back up, and you’ll end up with errors like “X is not a function” or “no such property X” errors.
For a real-world example, consider the following snippet of ClojureScript, which draws a circle using the excellent Raphael.js vector graphics library:
(let [image (js/Raphael. 10 50 320 200)] (.circle image 50 50 50))
This works great with Whitespace Only or
Simple compilation. It uses the
js/
namespace to call the global Raphael
function as a constructor, which creates a Raphael drawing object of
the specified dimensions. It then invokes the circle
method on that object to draw a circle.
But try it in
Advanced mode, and you’ll get a cryptic error,
something like new
Raphael(10, 50, 320, 200)).K is not a
function
. Initially, this doesn’t make sense: what
is the K
function it’s trying to invoke?
In this case, K
is the munged name of
circle
. Google Closure doesn’t know that
circle
is a name that needs to be preserved. It has no
knowledge of the Raphael library at all, since that wasn’t included
in its compilation pass.
What’s needed is a way to inform the Google Closure Compiler
that circle
is an external reference, and should be
left alone when compiling. Fortunately, Google provides such a
mechanism: it allows you to create an externs
file.
An externs file is just a JavaScript file that contains a JavaScript variable and property declarations. Any variable or property referenced in the externs file will not be munged. It isn’t actually compiled itself, so the variable references don’t have to be meaningful, they just have to be present to signal the compiler not to munge them. A very simple externs file that would make the Raphael code work would be something like this:
var Raphael = {}; Raphael.circle = function() {};
Of course, Raphael has many more functions than just
circle
, but you only need to declare the ones you want
to consume from ClojureScript.
Finally, you need to tell the ClojureScript compiler about the
externs file using the :externs
compiler options, which
is a sequence of strings of the classpath-relative paths of extern
files. For example, to include this externs file for Raphael:
{:output-to "resources/public/js/main.js" :optimizations :advanced :externs ["raphael_externs.js"]}
That’s it! If you’ve included the necessary references in an externs file, then you can reference variables and properties from an outside context without munging, and successfully consume any JavaScript library you like.
It’s fairly straightforward to use ClojureScript to write libraries for distribution and consumption by other applications. You will need to package it differently, however, depending on whether you intend clients of your library to consume it using ClojureScript or JavaScript (Figure 7-1).
The best way to distribute a ClojureScript library for use by ClojureScript is to distribute the *.cljs source files directly, either by giving clients a directory full of source code or distributing a JAR file containing the *.cljs files. In either case, the clients will need to add the directory or JAR to their compiler classpath so the ClojureScript compiler can find them.
All the client has to do to use the library is to consume it as described in the section above on consuming ClojureScript libraries.
If you want JavaScript applications to be able to call your
ClojureScript code, you’ll need to distribute the
compiled version of your app. Typically, the
easiest way to do this is to compile your library to a single
*.js file using the Google Closure Compiler and
give that file to your clients. They can then reference that file in a
<script>
tag on their page, and start using
it.
Fortunately, actually using the library should be fairly easy.
ClojureScript functions are just JavaScript functions, and namespaces
are just nested objects (following the JavaScript convention). So, for
example, if you have a ClojureScript function
foo.bar/hello-world
, your JavaScript clients can easily
call it using foo.bar.hello_world()
.
You will have to be careful not to expect arguments or return objects they won’t be able to use easily, such as ClojureScript vectors or maps. If you really intend your library to be used extensively from JavaScript, you’ll probably want to create a set of public API functions that accept and return more familiar types. For example, you might convert ClojureScript maps to JavaScript objects, sequences to JavaScript arrays, and keywords to strings before returning them.
If you want to compile your libraries with Advanced mode, you’ll need to make one small additional change. JavaScript libraries can reference ClojureScript vars by name, but Advanced mode compilation munges all the var names. If you want to be able to reference a var from external JavaScript, you’ll need to mark it specifically for preservation.
To do this, tag the vars whose names you want to preserve using
the :export
metadata tag. ClojureScript metadata is data
that can be attached to any ClojureScript object, and since it can be
inspected by the compiler it can be used to alter the emitted
JavaScript. In the case of :export
, it indicates that the
tagged var should not be munged.
Adding the :export
metadata tag to a function using
the metadata reader macro looks like this:
(ns foo) (defn ^:export hello [name] (js/alert (str "Hello, " name)))
This code defines a very simple function called
hello
in the foo
namespace, but tags it with
:export
so that it won’t be munged during advanced
compilation. This means that a JavaScript caller can invoke it
directly: foo.hello("Luke")
would result in an alert box
popping up that says “Hello, Luke”.