How to Build Clojurescript Libs with JavaScript Dependencies

Using JavaScript dependencies in a Clojurescript library seems to be hard. It took me many hours to understand how it should work. A big thanks to Chas Emerick for setting me straight on most of this.

Luke Vanderhart posted a general introduction to using Javascript libraries in Clojurescript. Go read it if you haven't already - this post assumes you have.

While that post is an excellent description of using JavaScript in a Clojurescript application, it doesn't really address JavaScript in Clojurescript libraries, which has the additional problem of how to ensure the JavaScript dependency is available in the consumer of the library. A Clojurescript library should definitely be capable of providing it's dependencies, but should also allow the consumer to override the version of these dependencies.

Don't package the JavaScript

The first approach is to simply not provide the JavaScript at all. This is the approach taken by jayq for example. The consumer of jayq, or any library that uses jayq, is required to provide jQuery through the JavaScript runtime. This can take the form of a <script> link in the browser page, or a call to webPage#injectJs in phantomJS. The compile :libs or :foreign-libs options can not be used to provide the dependency, as there is no way for the compiler to know that jayq depends on the namespace provided by these options.

For the consumer of the library to use compiler:optimizations other than :whitespace, they will need to provide an :externs file.

Package JavaScript

The second approach is to package the JavaScript via a Clojurescript namespace. This involves adding a require on a namespace to the code that directly depends on the JavaScript, and arranging for that Clojurescript namespace to load the JavaScript, using either of the compiler:libs or :foreign-libs options.

The Clojurescript library can make the JavaScript library available in its resources. The library consumer can then use resource via the :libs or :foreign-libs options, depending on whether or not the JavaScript contains a goog.provides call.

If the library is packaged with a goog.provides call, then the consumer can not replace the version using :libs [""] - the use of an explicit prefix in :libs is needed to prevent more than one JavaScript implementation trying to provide the clojure namespace, or the use of :foreign-libs where the namespace is explicitly mapped.

For examples, the pprng library packages its dependency with a goog.provides call, allowing the use of :libs [""] to pull in the dependency. The papadom library on the other hand provides vanilla javascript dependencies, and requires the use of the more verbose :foreign-libs option.

If the JavaScript is to be provided in the runtime, then the consumer will have to provide an empty namespace definition to satisfy the require in the Clojurescript library, and the:externs file as in the first case.

Postscript

There are several assumptions in much of the documentation that I didn't see explicitly explained. I'll record these here for posterity.

A clojurescript library is always a source code library. There is no such thing as the linking of compiled clojurescript artifacts.

Neither:libs nor:foreign-libs actually changes how the JavaScript is accessed within the clojurescript code. If you include jQuery via a :libs, and a require, you still access it through js/jQuery. The require of the namespace specified by goog.provide, or the namespace specified in the :foreign-libs' :provides key, simply ensures the JavaScript is loaded.

The choice of compiler :optimizations affects what information you need to provide, and this differs depending on whether you are providing javascript libraries through the runtime (e.g. $lt;script> tags in the browser), or through :libs or :foreign-libs compiler options. The simplest here is to use the compiler options. When providing the JavaScript via the runtime, then everything should also just work if you are using no optimisation, or just :whitespace, but as soon as you try anything else, you will need to provide an :externs definition for the JavaScript libraries.

Discuss this post here.

Published: 2013-08-16

Exploring a todo app with core.async

We're going to build an equivalent of the AngularJS TODO example using core.async, and a templating library, papadom, that I've written to help in this.

Clojurescript recently gained a CSP implemetation via core.async, similar to Go's channels, or CML's channels (CML also has a nice select). Bruce Haumann started exploring this with ClojureScript Core.Async Todos, and David Nolen has been looking at how to use core.async for responsive design. In this post, we'll take the TODO example, and take it a little further.

Basic Display

We'll start with just displaying a list of todo items. For this we'll need a template, so we'll just write this in HTML, and add a t-template attribute, which enables us to use mustache style templating of values to display. This doesn't use mustache sections for looping, in order to preserve valid HTML markup.

<h1>TODOS</h1>
<ul class="unstyled">
  <li t-template="todos">{{text}}</li>
</ul>

To get this to show something we'll need some code:

(ns app
  (:require
   [papadom.template :refer [compile-templates render]]))

(defn start []
  (compile-templates)
  (render {:todos [{:text "learn papadom" :done false}
                   {:text "write a papadom app" :done false}]}))

When you call app.start() from the page containing the above template, you'll see a list of two todo entries.

Adding an event

Now we have something displayed, lets add a checkbox to mark todo items as done:

<ul class="unstyled">
  <li t-template="todos">
    <input type="checkbox" t-prop="done" t-event="done"
           t-id="index" index="{{@index}}">
    <span>{{text}}</span>
  </li>
</ul>

The t-prop attribute tells the template to which data value to display as the checkbox.

The t-event attribute specifies that we want an event. When the checkbox is clicked, we will get a core.async message with a :done event type. We need to know which todo was clicked, so we use the t-id attribute to list the attributes whose values should be sent as the event data – in this case the index, which has a value based on handlebars style @index property.

Now we need some code to process the events. To do this we'll define an app function that will be passed a state atom containing a map with our todos state, and a core.async channel from which to read events. The function will loop over events, and dispatch them as required.

(defn app
  [state event-chan]
  (go
   (loop [[event event-data] (<! event-chan)]
     (case event
       :done
       (let [nv (boolean (:checked event-data))]
         (swap! state assoc-in [:todos (:index event-data) :done] nv)))
     (recur (<! event-chan)))))

When the app function receives a :done event, it will update the state atom appropriately. Now we have our state updating, we'll need to display it, which we can again do with the render function.

(defn show-state [state]
  (render "state" state))

We still need to get show-state called, and we'll arrange this in a modified start function. This will create an atom for the state, and add a watch on the atom that will call show-state.

(defn start
  []
  (let [event-chan (chan)
        state (atom nil)]
    (compile-templates)
    (template-events event-chan)
    (add-watch state :state (fn [key ref old new] (show-state new)))
    (reset! state {:todos [{:text "Learn papadom" :done false}
                           {:text "Build a papadom app" :done false}]})
    (app state event-chan))))

We've also added a core.async channel, event-chan, which we've passed to template-events to arrange delivery of the events defined in our template. We pass this channel to the app function to start processing the events.

This shows the basic structure of the application.

Adding New Todo Elements

To allow you to add new todo items, we'll add a form to our template, specifying a t-event attribute, which will cause an event to be sent when the form is submitted, with the form's input values as the event data.

<form t-event="add-todo">
  <input type="text" t-prop="text" size="30" placeholder="add new todo here">
  <input class="btn btn-primary" type="submit" value="add">
</form>

To process this new event, we'll add a case to the app function loop's case form.

:add-todo
(swap! state update-in [:todos]
       conj {:text (:text (input-seq->map event-data))
             :done false})

This uses the input-seq->map helper to convert the data from the form into a map, and we extract the :text value (defined by t-prop in the input element).

And we're done. To see a full working example have a look at the template and code in the todo example of papadom. To run the example:

git clone [https://github.com/hugoduncan/papadom.git](https://github.com/hugoduncan/papadom.git)
cd papadom/examples/todo
lein ring server

Discuss this post here.

Published: 2013-08-15

Archive