Diving into clojurescript
What is it
Hosted language
A language that targets different platforms
Sits and integrates on a host platform
Maintains access to host and its ecosystem
Hosted language
Clojure ☛ JVM
ClojureScript ☛ JavaScript
JavaScript Runtimes:
Browsers
Node.js
RhinoAbout the Language
Lisp family
(((((())((((((()))))()()(((())))))))
Functional programming language
- Immutability by default
-
First class functions
- Programming with values
- Declarative-ish (map, filter, etc)
A practical language
-
Not pure fp
- Really easy to interop
-
You can step out of the defaults
when necessary
Differences from others
How is it different from coffeescript, typescript, ...?
Changes some JavaScript semantics (to be saner)
- Equality (values)
- Falsy/Truthy
- Sane scoping (-hoisting)
Provides a consistent full language and ecosystem
- Modules
- Package management
- Great standard lib
- Good practices and philosophy
Cool stuff in clojurescript
Values FTW
Immutable by default
Cool stuff in clojurescript
Concise and expressive
Cool stuff in clojurescript
To the code!
Super basics
Setup
- Lighttable
- Git
- Leiningen
Small intro to Lighttable
Pmeta + SpaceWorkspaces
Tabs and Tabsets
Instarepl
Evaling stuff
Code Samples
github.com/joakin/diving-into-clojurescript-exercises
git clone git@github.com:joakin/diving-into-clojurescript-exercises.git
Connect to Lighttable UI
Code samples are in basic/
Prerequisites 01-pre.cljs
Exercise 01.cljs
Solutions are in 01-sol.cljs
Reference
Basic syntax
Synonyms
himera.herokuapp.com/synonym.htmlclojure.core
http://clojure.github.io/clojure/clojure.core-api.html
http://clojuredocs.org/clojure_core/1.3.0/clojure.core
Interactive TUTORIAL
git clone git@github.com:swannodette/lt-cljs-tutorial.git
Add lt-cljs-tutorial to workspace
Open lt-cljs-tutorial.cljs
Add Connection: Lighttable UI
Start evaling!
Getting the hang
Do it yourself
Check the docs
Look at the community results (Follow top members)
Lets make some apps
The artsy wall
We are going to walk through the creation of a canvas fullscreen app
A wall of colorful squares that vibrate
joakin/diving-into-clojurescript-exercises/futurejs-wall
Steps are stored in src-steps (see README)
Interesting things to learn
- Live coding
- Namespaces
- Including external libraries
- Source maps
- JS interop
LT plugins web app
We are going to make a web app that will consume a service
via ajax and will render the contents.
joakin/diving-into-clojurescript-exercises/lt-plugins
Steps are in the README
Project management
- Create new projects (templates)
- Fetch dependencies for your project
- Run tests
- Run a fully-configured REPL
- Compile sources
- Run the project
- Compile and package projects for deployment
- Publish libraries to repositories such as Clojars
- Automation tasks (leiningen plug-ins)
lein help tutorial | less
Creating New projects
lein help new
lein new mies awebsite
How do I compile ClojureScript?
cljsbuild
Leiningen plugin for compiling ClojureScript
In project.clj ☞
:plugins [[lein-cljsbuild "1.0.2"]]
Sample conf:
:cljsbuild {
:builds [{:id "awebsite"
:source-paths ["src"]
:compiler {
:output-to "awebsite.js"
:output-dir "out"
:optimizations :none
:source-map true}}]})
Compiling:
lein cljsbuild once awebsite # just build once
lein cljsbuild auto awebsite # watches changes and recompiles incrementally
Development workflow
- Launch compiler watch
- Open project in LightTable
- Open browser tab with index.html
- Live eval!
- Docs
- Autocomplete
- Watches
Compilation modes
Clojurescript compiler features several compilation modes
project.clj with all the options commented
Each mode has different tradeoffs:
- Development speed
- Multiple files vs 1 file
- Code minification
- Code optimizations
Clojurescript compiler ☞ cljs ► standard library & js
Google closure compiler ☞ merging files, minification, optimizations
Optimizations: none
- No optimizations
- Lots of files
- Fastest compilation (& incremental)
- Dev oriented
- Specific imports in html
:cljsbuild {
:builds [{:id "awebsite"
:source-paths ["src"]
:compiler {
:output-to "awebsite.js"
:output-dir "out"
:optimizations :none
:pretty-print true
:source-map true}}]}
<script src="out/goog/base.js" type="text/javascript"></script>
<script src="awebsite.js" type="text/javascript"></script>
<script type="text/javascript">goog.require("awebsite.core");</script>
Optimizations: Whitespace
- Cleans whitespace and comments
- One file
- "Fast" compilation (less than none)
- Readable JS
:cljsbuild {
:builds [{:id "awebsite"
:source-paths ["src"]
:compiler {
:output-to "awebsite.js"
:output-dir "out"
:optimizations :whitespace
:pretty-print true
:source-map true}}]}
<script src="awebsite.js" type="text/javascript"></script>
Optimizations: SIMPLE
- One file with simple optimizations
- Slower compilation
- One import in the html
:cljsbuild {
:builds [{:id "awebsite"
:source-paths ["src"]
:compiler {
:output-to "awebsite.js" ; No output-dir (only the file)
:optimizations :simple
:pretty-print true
:source-map "awebsite.js.map"}}]} ; Sourcemaps are a file now, not true
<script src="awebsite.js" type="text/javascript"></script>
Optimizations: ADVANCED
- One file (smallest size)
- Slowest compilation
- One import in the html
- Agressive optimizations, minification and code rewrites
- Performance and size gains
:cljsbuild {
:builds [{:id "awebsite"
:source-paths ["src"]
:compiler {
:output-to "awebsite.js"
:optimizations :advanced}}]}
<script src="awebsite.js" type="text/javascript"></script>
Notes
Options pretty-print and source-maps are optional.
They increase compilation time.
(Incrementally not so much)
Suggestions
Use :optimizations :none with :pretty-print true and :source-maps true
for development workflows (using lein cljsbuild auto (pure delight))
Use :optimizations :advanced for staging and production.
(Staging with :source-maps and :pretty-print, production without).
Some informal benchmarks
lein cljsbuild once
Successfully compiled "awebsite.js" in 9.214 seconds.
Successfully compiled "awebsite-w.js" in 14.045 seconds.
Successfully compiled "awebsite-s.js" in 19.269 seconds.
Successfully compiled "awebsite-p.js" in 18.701 seconds.
lein cljsbuild auto
Successfully compiled "awebsite.js" in 0.05 seconds.
Successfully compiled "awebsite-w.js" in 4.126 seconds.
Successfully compiled "awebsite-s.js" in 5.918 seconds.
Successfully compiled "awebsite-p.js" in 4.188 seconds.
Some informal sizes
To be fair, in out there are also cljs and sourcemap files, so size is smaller
(about 900Kb)
Note that with advanced, we got a 10x reduction on size.
(And it's probably faster)
Libraries
We can distinguish 3 kinds of libraries that we can use
(and require different treatment)
- ClojureScript libraries
- Google Closure compatible libraries
- Other libraries from the JS ecosystem
Clojurescript libraries
Hosted in lein compatible repos (clojars)
How to find them:
Or Google
Just include them in your project.clj and go:
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-2173"]
[rm-hull/monet "0.1.11"]]
Restart the compiler so that it downloads it
Google Closure
Big set of cross browser libraries written by Google
Example:
API, importing & real_world_usage
Lots of interesting stuff: Format, Color, Events, Dom, Style, ...
Takes advantage of Advanced compilation
(deletes code you don't use and inlines code where it can)
Google Closure
The compiler has and includes as necessary a version of the closure libs
You can specify a specific version of the google closure libraries:
:dependencies [[org.clojure/clojure "1.5.1"]
[com.google.javascript/closure-compiler "v20131014"]
[org.clojure/clojurescript "0.0-2173"
:exclusions [com.google.javascript/closure-compiler]]
Google closure compatible libs
Libraries written with the closure compiler optimizations in mind.
Not a whole lot of them.
They take advantage of code optimizations and compiler errors (docs)
Example (Download react to your project):
:compiler {
; ... rest of arguments
:foreign-libs [{:file "reactjs/react.js"
:provides ["React"]}]
:externs ["reactjs/externs/react.js"]}
Other libraries
Almost all of the JS libs.
Include the code in your project,
If it has externs, you include them:
:preamble ["react/react_with_addons.min.js""gifjs/dist/gif.js"] ; or link them in the html:externs ["react/react_with_addons.js""gifjs/dist/gif.js"]
Most of them don't so, options:
- Include the library as its own externs
- Try to autogenerate them with lein-externs
- Write a dummy externs-whatever.js
Other libraries
Pros:
HUGE ecosystem
Lots of good libraries
(moment.js, react.js, hammer.js, bluebird, async, d3.js, bacon.js, ...)
Cons:
No optimizations or unused code removal
Bigger size of bundle because of libraries
We have to take care of externs with advanced
Suggestion:
If there is a good cljs lib, use that. Otherwise, go wild.
Some Cool cljs libraries
Secretary | Client side router |
Processing.cljs | Wrapper over processing.js |
Dommy Enfocus Domina | Dom manipulation and templating library |
Om Reagent | Interface to React.js (web apps) |
Javelin | Spreadsheet-like dataflow programming |
Monet | Easy and performant canvas and visuals |
Jayq | Idiomatic cljs - jQuery wrapper |
cljs-ajax cljs-http | HTTP and AJAX library |
Lucuma | Web components |
Servant | Web workers |
cljs-time | Date and time library |
Inkspot | Colors and swatches |
...
Testing
Live development is no substitute for tests
Several options:
clojurescript.test
clojurescript.test
Full complete test library (port of the clojure version)
Well mantained and documented
To use it just add it to your plugins in project.clj
:plugins [[com.cemerick/clojurescript.test "0.3.0"]]
- Run tests from the repl
- Run tests each time cljsbuild compiles
:cljsbuild {:builds [ ... ]
:test-commands {"unit-tests" ["phantomjs" :runner
"this.literal_js_was_evaluated=true"
"target/cljs/testable.js"
"test/cemerick/cljs/test/extra_test_command_file.js"]}}
:test-commands {"unit-tests" ["node" :node-runner
; extra code/files here...
]}
clojurescript.test
Simple and easy to understand
(deftest somewhat-less-wat
(is (= true (some-function "whatever"))))
Both sync and async support
(deftest ^:async timeout
(let [now #(.getTime (js/Date.))
t (now)]
(js/setTimeout
(fn []
(is (>= (now) (+ t 2000)))
(done))
2000)))
specljs
BDD style testing (specs)
:dependencies [[org.clojure/clojure "1.5.1"]]
:profiles {:dev {:dependencies [[speclj "3.0.0"]]}}
:plugins [[speclj "3.0.0"]]
:test-paths ["spec"]
:cljsbuild {:builds {:dev {:source-paths ["src/cljs" "spec/cljs"]
:compiler {:output-to "path/to/compiled.js"}
:notify-command ["bin/speclj" "path/to/compiled.js"]}
:test-commands {"test" ["bin/speclj" "path/to/compiled.js"]}}
(ns sample.core-spec
(:require-macros [speclj.core :refer [describe it should should-not run-specs])
(:require [speclj.core]
[sample.core :as my-core]))
(describe "Truth"
(it "is true"
(should true))
(it "is not false"
(should-not false)))
(run-specs)
Purnam
Jasmine based testing (used with Karma)
:dependencies [[im.chit/purnam "0.4.3"]]
Example
(describe "Addition"
(it "should add things"
(is (+ 1 1) 2)))
Example config and video for running with karma
Purnam also contains a bunch of macros for making
js interop more similar to JS than to ClojureScript.
Others
There are more, and also wrappers for JS libraries.
You could ignore all this and just use a JS library for testing (interop)
(Why?)
Property-based testing
Instead of enumerating
expected input and output for unit tests,
you write properties about your
function that should hold true for all inputs.
(def sort-idempotent-prop
(prop/for-all [v (gen/vector gen/int)]
(= (sort v) (sort (sort v)))))
(tc/quick-check 100 sort-idempotent-prop)
You can usit standalone or integrated in your clojurescript.test suite
(defspec first-element-is-min-after-sorting ;; the name of the test
100 ;; the number of iterations for test.check to test
(prop/for-all [v (such-that not-empty (gen/vector gen/int))]
(= (apply min v)
(first (sorted v)))))
Macros
Code generating/rewritting code that gets run at compile time.
(Code is data, as you have seen)
ClojureScript macros are written in clojure (.clj files)
Reading, evaluation and macros
Super powerful, useful, language is infinitely extensible by users
Complicated to debug, best avoided unless necessary (maybe)
Examples
(defmacro when
"Evaluates test. If logical true, evaluates body in an implicit do."
{:added "1.0"}
[test & body]
(list 'if test (cons 'do body)))
(macroexpand '(when (the-cows-come :home)
(call me :pappy)
(slap me :silly)))
; =>
(if (the-cows-come :home)
(do (call me :pappy)
(slap me :silly)))
(defmacro unless
"Inverted 'if'"
[test & branches]
(conj (reverse branches) test 'if))
(macroexpand '(unless (done-been slapped? me)
(slap me :silly)
(say "I reckon that'll learn me")))
; =>
(if (done-been slapped? me)
(say "I reckon that'll learn me")
(slap me :silly))
So...
They are a tough topic, and great books and articles are available.
Look for Clojure macros, not ClojureScript specifically
Super powerful, enable users to extend the language via libraries
in ways inconceivable in other languages (JS? Damn ECMA)
We will see the power in the next section.
Some uses:
Rewrite code (unless, core.async)
New syntax constructs
Optimize code (compile time) (dommy)
Sweet JS
Mozilla leaded effort to have macros in JavaScript
Opt-in macros
More verbose because syntax (code is not data),
but brings the power and responsibility to JS
Example: ES6 syntax, Ki (lisp+mori)
superpowers
Great philosophy, vision, community and people
with a great language produce awesome powerful things
core.async
Go style concurrency (CSP) implemented as a library!
Turns syncronous looking code into CPS (callbacks)
Concurrent code in single threaded JS!
(defn init []
(let [clicks (listen (dom/getElement "search") "click")
results-view (dom/getElement "results")]
(go (while true
(<! clicks)
(let [[_ results] (<! (jsonp (query-url (user-query))))]
(set! (.-innerHTML results-view) (render-query results)))))))
(defn get-user [id]
(let [out (chan)]
(db-query-user {:id id} (fn [user] (put! out user)))))
(defn update-user [user]
(db-update-user user (fn [err] (put! out err))))
(def handler [req res]
(go
(let [user (<! get-user (.id req))
result (<! update-user user)]
(.send res (if (nil? result) "OK" "BROKE"))))
Core.async
(let [c1 (chan)
c2 (chan)]
(go (while true
(let [[v ch] (alts! [c1 c2])]
(println "Read" v "from" ch))))
(go (>! c1 "hi"))
(go (>! c2 "there")))
ttp://swannodette.github.io/2013/08/31/asynchronous-error-handling/
(go (try
(let [tweets (<? (get-tweets-for "swannodette"))
first-url (<? (expand-url (first (parse-urls tweets))))
response (<? (http-get first-url))]
(. js/console (log "Most recent link text:" response)))
(catch js/Error e
(. js/console (error "Error with the twitterverse:" e)))))
10.000 concurrent processes
100.000 DOM updates
Local event loops
FRP
Core.match
An optimized pattern match and predicate dispatch library for Clojure.
(doseq [n (range 1 101)]
(println
(match [(mod n 3) (mod n 5)]
[0 0] "FizzBuzz"
[0 _] "Fizz"
[_ 0] "Buzz"
:else n)))
(let [x 1 y 2]
(match [x y]
[1 b] b
[a 2] a
:else nil))
(let [x {:a 1 :b 1}]
(match [x]
[{:a _ :b 2}] :a0
[{:a 1 :b 1}] :a1
[{:c 3 :d _ :e 4}] :a2
:else nil))
(let [x [1 2 3]]
(match [x]
[[_ _ 2]] :a0
[[1 1 3]] :a1
[[1 2 3]] :a2
:else :a3))
core.logic
Prolog-like relational programming, constraint logic programming,
and nominal logic programming for Clojure
(run* [q]
(== q true))
;;=> (true)
(run* [q]
(membero q [1 2 3])
(membero q [2 3 4]))
;;=> (2 3)
(run* [q]
(== {:a q :b 2} {:a 1 :b 2}))
;;=> (1)
(run* [q]
(fresh [a]
(membero q [1 2 3])
(membero a [3 4 5])
(== q a)))
;;=> (3)
core.typed
Gradual typing in Clojure, as a library.
(ann ^:no-check foo (Fn [Number -> Number]))
(defn foo [a]
'a)
(ann bar [Number -> Number])
(defn bar [b]
(+ 2 (foo b)))
closeup
Learn new things that suppose significant brain effort
Bring what you learned to your daily work (at least the concepts)
Embrace immutability (eases reasoning about code)
Play and have fun
Where to get help
Joaquin
chimeces.com/about
@joakin
github.com/joakin
joaquin@chimeces.com
Diving into ClojureScript
By Joaquin Oltra
Diving into ClojureScript
- 9,475