-
Notifications
You must be signed in to change notification settings - Fork 790
Dependencies
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.
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.
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))
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.
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.clj
file:
(cljs.closure/build "src" {:output-to "out/main.js"
:externs ["yayquery-externs.js"]
:optimizations :advanced})
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 ClosureScript.
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 ((aget js/window "yayQuery"))]
((aget yay "sayHello") ((aget 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.
To maximize efficiency of content delivery, you can bundle JavaScript code along with your compiled ClojureScript 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 in your build.clj
file.
(cljs.closure/build "src" {:output-to "out/main.js"
:libs ["yayquery.js"]
:optimizations :advanced})
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.
Discussion of bundling non-GCC compatible code.
Integrating libraries packaged with CLJSJS.
How to compile ClojureScript JARs without external tooling.
Explanation of Maven/Leiningen.
- Rationale
- Quick Start
- Differences from Clojure
- [Usage of Google Closure](Google Closure)