Shown at Feb '14 Den of Clojure
So you've read the blogs and thought how fun it would be to write web front ends with your favorite lisp. Perhaps you've even fired up the repl and done a little playing. But how do you get from there to a real-life app?
This talk will give a brief introduction to ClojureScript, cover using Austin to drive a browser REPL from within Emacs, configuring the Google Closure compiler to make any JavaScript library usable from ClojureScript, and finally put it all together in a map-based REST-backed single page web application.
ClojureScript is a Clojure implementation compiling to JavaScript.
It's heavily dependent upon Google Closure compiler for optimization.
Not 100% of Clojure, but all that makes sense:
- No concurrency support - JavaScript is single-threaded.
Out of the box: Rhino and browser-connected.
Austin > ClojureScript's repl:
- nREPL via Piggieback
- Lein middleware
- Internal HTTP server
Server:
- Ring - REST
- Compojure - routing
- lein-cljsbuild
Client:
- Austin - REPL
- ClojureScript libs: jayq, dommy, domina, ...
- Closure for interop with LeafletJS, Backbone, etc.
Like with Clojure, it's both important and powerful. The underlying platform is exposed.
Luke Vanderhart has a great article on Using JavaScript libraries in ClojureScript. The summary is "use externs definitions to use JavaScript libraries".
L = {};
L.map;
L.Map;
L.map.addLayer;
L.map.setView;
+---------------------+
| nREPL |
|---------------------|
| | +------------+
+-----------+ +-----------+ | +-----------+ | | |
| | | | | | | | HTML CSS JS | |
| .clj +---->| Cider +------+ Ring +-------------------->| |
| | | | | | | | AJAX | |
+-----------+ +-----------+ | +-----------+ | | |
| ^ | | |
| | | | Browser |
| | | | |
+----------------------------------+ | | |
| | | | |
+-----+-----+ +-----------+ | +-----------+ | | |
| | | | | | | | | |
| .cljs +---->| Cider +------+ Austin +---------------------+ |
| | | | | | | | REPL | |
+-----------+ +-----------+ | +-----------+ | JS updates | |
| | +------------+
+---------------------+
Simple Leaflet map showing all the bike racks in Denver, using data from open data services (OpenStreetMap, Denver Open Data)
Technically, also want to prove:
- ClojureScript/Clojure client/server workflow
- Refreshless development
- Non-trivial interop with extant JS libs
Caveats:
- This isn't the best pure ClojureScript way to build a front end. Consider Om, core.async, and simple idiomatic template libs.
app.clj
:
;; in requires:
[ring.middleware.params :refer [wrap-params]]
;;
(defn keep-n-features [feature-collection n]
(assoc feature-collection
:features (vec (take (Integer. n) (:features feature-collection)))))
(defroutes api-routes
(GET "/bike_racks/" [n]
(-> all-bike-racks
(keep-n-features (or n 1))
json/write-str
resp/response)))
Test by running (retrieve-geojson)
in den-of-cljs.js-app.
backbone-externs.js
:
Backbone.on;
Backbone.trigger;
js-app.cljs
:
(defn retrieve-geojson
([count]
(let-ajax [geojson {:url (str "/api/bike_racks/?n=" count)}]
(swap! model (fn [m] (assoc m :feature-geojson geojson)))))
([] (retrieve-geojson 1000)))
(defn ^:export init []
(.on js/Backbone "change-count" (fn [& args]
(retrieve-geojson (first args))))
(leaflet-map/init! model)
(leaflet-map/start!)
(retrieve-geojson))
Demo with (.trigger js/Backbone "change-count" 20)
- Testing ClojureScript
- ClojureScript MVC/MVP + idiomatic patterns for a SPWA
- Basic DOM manipulation
- Events
- AJAX
- Deployment to Heroku
- Remote debugging (and hot fixes?)