ClojureScript

Dependencies

This guide requires ClojureScript 1.10.238 or later and assumes familiarity with the Quick Start.

Every non-trivial ClojureScript application will eventually need to consume code created by others. ClojureScript developers can of course take advantage of code authored using ClojureScript. However, ClojureScript developers can also consume arbitrary JavaScript code, whether or not it was written with ClojureScript in mind.

This guide assumes you’ve worked through the Quick Start guide, and are equipped with the dependencies introduced there.

Consuming JavaScript Code

While you can consume any JavaScript code, the optimal mechanism for including that code is not always the same. The following sections explore the various options for utilizing third party JavaScript code.

Closure Library

The easiest JavaScript code to consume is that of Google’s Closure Library (GCL), which is automatically bundled with ClojureScript. GCL is a massive collection of JavaScript code organized into namespaces much like ClojureScript code itself. Thus, you can require a namespace from GCL in the same fashion as a ClojureScript namespace. The following example demonstrates basic usage:

(ns hello-world.core
  (:require [goog.dom :as dom]
            [goog.dom.classes :as classes]
            [goog.events :as events])
  (:import [goog Timer]))

(let [element (dom/createDom "div" "some-class" "Hello, World!")]
  (classes/enable element "another-class" true)
  (-> (dom/getDocument)
    .-body
    (dom/appendChild element))
  (doto (Timer. 1000)
    (events/listen "tick" #(.warn js/console "still here!"))
    (.start)))

See this blog post on the difference between :import and :require for closure libs.

The gist of it is: use :import for Closure classes and enums, and use :require for everything else.

External JavaScript Libraries

In cases where GCL doesn’t contain the functionality you want, or you’d otherwise like to take advantage of a third party JavaScript library, you can use the code directly.

Let’s consider the case where we want to use a fancy JavaScript library called yayQuery. To utilize a JavaScript library, simply reference the JavaScript as normal. Whether the file is loaded externally or inline makes no difference, both will be applied in the same fashion at runtime. To make things simple, we’ll define this library inline:

<script type="text/javascript">
    yayQuery = function() {
        var yay = {};
        yay.sayHello = function(message) {
            console.log(message);
        }
        yay.getMessage = function() {
            return 'Hello, world!';
        }
       return yay;
    };
</script>

To use this library from ClojureScript, we can simply refer to the symbols directly. If you build the following code using {:optimizations :none}, everything will work fine and you will see a message in your JavaScript console.

(ns hello-world.core)

(let [yay (js/yayQuery)]
  (.sayHello yay (.getMessage yay)))

While this works fine with unoptimized code, it will fail when we use advanced optimizations. Try compiling the same code with {:optimizations :advanced} and reload your browser. You will receive an error message similar to the following (it may not be exactly as below):

Uncaught TypeError: sa.B is not a function

Why did this happen? When using advanced optimizations, the Google Closure Compiler will rename symbols. In most cases, this is not a problem, as all instances of the same symbol will be renamed consistently. However, in this case the external symbol (the name in the JavaScript code) is separate from our compilation unit, so the names no longer match. Fortunately, we have options for resolving this issue without losing all of the benefits of advanced compilation.

Using Externs

To fix compilation without modifying your source code at all, you can add an externs file. An externs file defines the symbol names in a given library, and is used by Google Closure Compiler to determine which symbols must not be renamed. Here’s a minimal externs file for our yayQuery library:

var yayQuery = function() {}
yayQuery.sayHello = function(message) {}
yayQuery.getMessage = function() {}

Assuming this file is named as yayquery-externs.js, you can reference it as follows in your build.edn file:

{:output-to "out/main.js"
 :externs ["yayquery-externs.js"]
 :optimizations :advanced})

It is important to understand that all paths referenced in the :externs vector must be on the classpath. For example you might have placed the above externs file under a resources directory. Then when using the standalone ClojureScript JAR you must launch your build script with the following:

clj -M -m cljs.main -co build.edn -c

Recompile with the externs file referenced, and your code should work again without any modifications. Note that for many popular JavaScript libraries, you may be able to find externs files which have already been created by the library authors or the broader community. These files are useful for any developer taking advantage of Google Closure Compiler, even those not using ClojureScript.

Using String Names

For simple cases where you only reference a small number of JavaScript symbols, you can also change your source code to reference code by string name. Google Closure Compiler will never rename strings, so this style will work without needing to create an externs file. The code below will work in advanced compilation mode even without externs:

(let [yay ((goog.object.get js/window "yayQuery"))]
  ((goog.object.get yay "sayHello") ((goog.object.get yay "getMessage"))))

Careful readers may notice above that we are referencing js/window just as we did js/yayQuery in the failing example. It works in this case because Google Closure Compiler ships out of the box with a number of externs for browser APIs. These are enabled by default.

Bundling JavaScript Code

To maximize efficiency of content delivery, you can bundle JavaScript code along with your compiled ClojureScript code.

Google Closure Compiler Compatible Code

If your external JavaScript code has been written to be compatible with Google Closure Compiler, and exposes its namespaces using goog.provide, the most efficient way to include it is to bundle it using :libs. This bundling mechanism takes full advantage of advanced mode compilation, renaming symbols in the external JavaScript library and eliminating dead code. Let’s adapt our yayQuery library from previous examples, as below:

goog.provide('yq');

yq.debugMessage = 'Dead Code';

yq.yayQuery = function() {
    var yay = {};
    yay.sayHello = function(message) {
        console.log(message);
    };
    yay.getMessage = function() {
        return 'Hello, world!';
    };
    return yay;
};

This code is mostly identical to the previous inline version, but is now packaged within a "namespace" exposed using goog.provide. The library can be referenced easily in ClojureScript:

(ns hello-world.core
  (:require [yq]))

(let [yay (yq/yayQuery)]
  (.sayHello yay (.getMessage yay)))

To build the bundled output, use the following command:

clj -M -m cljs.main -co build.edn -O advanced -c

Because this code is compatible with advanced compilation, there is no need to create externs. If you look at the compiled output, you’ll see that the functions have been renamed and the unreferenced debugMessage has been completely eliminated by Google Closure Compiler.

While an extremely efficient way to bundle external JavaScript, most popular libraries are not compatible with this approach.

Bundling "Foreign" JavaScript Code

If the code you wish to bundle has not been authored with Google Closure Compiler compatibility in mind, you can include it as a foreign library. Foreign libraries are included in your final output, but are not passed through advanced compilation. Let’s consider a version of yayQuery which does not include a goog.provide:

yayQuery = function() {
    var yay = {};
    yay.sayHello = function(message) {
        console.log(message);
    };
    yay.getMessage = function() {
        return 'Hello, world!';
    };
    return yay;
};

Using code in foreign libraries from ClojureScript is very similar to using code that’s been included directly in the page via a <script> tag, with one key difference:

(ns hello-world.core
  (:require [yq]))

(let [yay (js/yayQuery)]
  (.sayHello yay (.getMessage yay)))

Notice the presence of :require in the ns declaration. This references a "namespace" called yq, but there is no corresponding goog.provide in the yayQuery file. In the case of foreign libraries, the "namespace" is provided in the build configuration. As long as the name in the :provides key matches what you :require and is unique across referenced libraries, you can name it anything you please:

{:output-to "out/main.js"
 :externs ["yayquery-externs.js"]
 :foreign-libs [{:file "yayquery.js"
                 :provides ["yq"]}]}

Note that we have re-introduced our externs file here. Though the foreign library is bundled, it must otherwise be referenced exactly as if the script had been included externally.

CLJSJS

The previous sections have discussed the various ways of integrating with any external JavaScript code. Finding the best way to integrate a library can be tricky, especially if you have to procure externs. Fortunately, for many of the most common JavaScript libraries, there is an easier way. The CLJSJS project automatically packages up external JavaScript libraries in a way that’s directly supported by the ClojureScript compiler. It will automatically package the best version of a library in a given context (including minified libraries when using advanced optimizations, for example), and automatically includes the appropriate externs.

Let’s say we’ve outgrown our beloved yayQuery library, and want to use jQuery instead. This is one of the many popular libraries which has been pre-packaged. We can fetch a copy as below:

curl -O https://clojars.org/repo/cljsjs/jquery/1.9.0-0/jquery-1.9.0-0.jar

If you take a peek inside the downloaded JAR file (unzip jquery-1.9.0-0.jar deps.cljs), you’ll see the contents of the bundled deps.cljs file:

{:foreign-libs
 [{:file "cljsjs/development/jquery.inc.js",
   :file-min "cljsjs/production/jquery.min.inc.js",
   :provides ["cljsjs.jquery"]}],
 :externs ["cljsjs/common/jquery.ext.js"]}

If you followed along with the previous sections, this should all be quite clear at this point. The :provides data tells us all we need to reference this code:

(ns hello-world.core
  (:require [cljsjs.jquery]))

(.text (js/$ "body") "Hello, World!")

The build file in this case is incredibly simple, as the library reference is entirely contained in the JAR which we’ll reference when we invoke the script:

{:output-to "out/main.js"}

Compile the code as below (note the addition of the JAR in our class path), and you should see the message display when you load your browser:

clj -M -m cljs.main -co build.edn -O advanced -c

Replacing a (transitive) CLJSJS dependency with another build of the library

Sometimes you have a transitive dependency on a CLJSJS library but want to include the dependency manually or use a custom build of it. In that case you need to do two things: (1) exclude the dependency with :exclusions and (2) create an empty namespace with the cljsjs name so that the build does not break.

For example om depends on cljsjs/react. To include a custom build you need:

;; project.cljs
;; ...
:dependencies [[org.omcljs/om "0.9.0" :exclusions [cljsjs/react]] ;; ...
;; src/cljsjs/react.cljs
(ns cljsjs.react)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
<script src="resources/public/js/compiled/your_cljs_code.js" type="text/javascript"></script>

Consuming ClojureScript Code

The ability to consume any JavaScript library makes ClojureScript an incredibly flexible and powerful language for writing JavaScript applications. Of course, ClojureScript developers can also easily include ClojureScript libraries authored by others.

Using Libraries Directly

Let’s make use of Schema, a ClojureScript library which enables us to validate complex data types. First, we need to procure a copy of the library:

curl -O https://clojars.org/repo/prismatic/schema/0.4.0/schema-0.4.0.jar

As with CLJSJS libraries, everything is packaged in a JAR file which we will reference in our class path when compiling. Unlike CLJSJS libraries, though, ClojureScript library JARs contain no externs or deps.cljs mappings.

Using the library is simple. Note that ClojureScript code and Clojure macros are packaged in the same library:

(ns hello-world.core
  (:require [schema.core :as s :include-macros true]))

(def Data {:a {:b s/Str :c s/Int}})

(s/validate Data {:a {:b "Hello" :c "World"}})

Our build script is even simpler:

{:output-to "out/main.js"}

Now, we can run the build. Simply reference the JAR as below:

clj -M -m cljs.main -co build.edn -c

Load up your browser, and you’ll see a helpful validation error from Schema in your JavaScript console. Change the :c key to an integer value and rebuild if you’d like to see this error go away.