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

Rhino

About 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


We will see more through the workshop!

To the code!

Super basics

Setup


  • Lighttable
  • Git
  • Leiningen



Small intro to Lighttable

Pmeta + Space
Workspaces
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

himera.herokuapp.com


Synonyms

himera.herokuapp.com/synonym.html

clojure.core

http://clojure.github.io/clojure/clojure.core-api.html
http://clojuredocs.org/clojure_core/1.3.0/clojure.core

Interactive TUTORIAL


swannodette/lt-cljs-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


4clojure.com


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


Leiningen


  • 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


Clojurescript templates


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


  1. Launch compiler watch
  2. Open project in LightTable
  3. Open browser tab with index.html
  4. Live eval!
  5. Docs
  6. Autocomplete
  7. 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)


  1. ClojureScript libraries
  2. Google Closure compatible libraries
  3. Other libraries from the JS ecosystem

Clojurescript libraries


Hosted in lein compatible repos (clojars)

How to find them:

Github search

Clojars

Google groups

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

API


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:

  1. Include the library as its own externs
  2. Try to autogenerate them with lein-externs
  3. 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.


    Double-check

    (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

    Writing 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


    StackOverflow


    Google Groups


    irc #clojurescript on freenode.net


    Joaquin


    chimeces.com/about

    @joakin

    github.com/joakin

    joaquin@chimeces.com

    Diving into ClojureScript

    By Joaquin Oltra

    Diving into ClojureScript

    • 5,362
    Loading comments...