Anton Astashov's blog

Would be nice to write something clever here.

Perfect ClojureScript Development Environment With Vim

| Comments

When I just started to learn ClojureScript, I had hard time trying to find some tutorials which could explain how to set up a nice dev environment for ClojureScript. The good environment is extremely important especially when you just started to play around with the language, it is very useful to get immediate feedback to your changes in the code. But it’s also helpful when you already know what you do, and allows you to iterate quickly on your project.

Later, while working on Textik (check it out, or check the sources), I was able to build something close enough to the ideal environment, which could allow me to iterate very quickly. To simplify life of ClojureScript beginners, here’s a tutorial how to set up that kind of environment.

So, here is a detailed explanation what you need to do from the scratch, or you could just go and clone/fork the skeleton project on Github, if you feel more like tl;dr.

I used Vim for development, but most of this tutorial (except the Vim part) is IDE-agnostic.

To start, let’s create a new Clojure project, called ‘perfection’:

1
$ lein new perfection

It will create a skeleton of a Clojure project. Then, open project.clj file, it should look something like that:

1
2
3
4
5
6
(defproject perfection "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]])

Add some lines to turn the project into a ClojureScript project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(defproject perfection "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojurescript "0.0-2277"]] ;; <- Adding ClojureScript

  :jvm-opts ["-Xmx1G"] ;; <- Sometimes ClojureScript compilation fails because available
                       ;; JVM heap space it too low. This line gives it more space

  :plugins [[lein-cljsbuild "1.0.3"]] ;; <- While you can build ClojureScript just by running
                                      ;;    'cljsc', there is a waaay more convenient way -
                                      ;;    using cljsbuild lein plugin

  ;; This is the list of builds 'cljsbuild' will use to compile our ClojureScript
  ;; code into JavaScript. Here we add first build "dev", it will put resulting main
  ;; entry for JavaScript code to resources/public/perfection.js, and all dependencies
  ;; for it to resources/public/out
  :cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src/perfection"]
              :compiler {
                :output-to "resources/public/perfection.js"
                :output-dir "resources/public/out"
                :optimizations :none
                :source-map true}}]})

Now, run

1
$ lein deps

And lein will install all the new dependencies we just added.

Next, delete the file src/perfection/core.clj, create a new file src/perfection/core.cljs, and put simple “Hello World” code into it:

1
2
3
(ns perfection.core)

(.log js/console "Hello world!")

Now we need something to run our compiled JavaScript code. Let’s create the index.html file, which will include the generated JavaScript file. Create resources/public/index.html file and add something like this to it:

1
2
3
4
5
6
7
8
9
10
11
<html>
  <head>
    <title>Perfection - perfect dev environment for ClojureScript</title>
    <link href="css/style.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <script src="out/goog/base.js" type="text/javascript"></script>
    <script src="perfection.js" type="text/javascript"></script>
    <script type="text/javascript">goog.require("perfection.core");</script>
  </body>
</html>

Let’s also add some styles, create the resources/public/css/styles.css CSS file with this content:

1
2
3
body {
  background: silver;
}

Let’s try to run compilation, via ‘cljsbuild’ lein plugin:

1
2
3
4
$ lein cljsbuild auto dev
Compiling ClojureScript.
Compiling "resources/public/perfection.js" from ["src"]...
Successfully compiled "resources/public/perfection.js" in 6.829 seconds.

Now we need some server to serve our files. I usually prefer to use Python’s SimpleHTTPServer:

1
$ python -m SimpleHTTPServer 8000

Next, open a browser and go to http://localhost:8000/resources/public/index.html. Check the console, you should see “Hello World!” there.

Figwheel

Now, you have some running ClojureScript environment, which will automatically and instantly (less than for a second) recompile your files when you change any files in ‘src/perfection’ directory. Sweet. But we want more.

Meet lein-figwheel, amazing lein plugin, which will reload the changed JavaScript files after recompilation in the browser without reloading the page! That is so cool. Let’s add it to our project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(defproject perfection "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojurescript "0.0-2277"]
                 [figwheel "0.1.3-SNAPSHOT"]]) ;; <- Adding Figwheel

  :jvm-opts ["-Xmx1G"]

  :plugins [[lein-cljsbuild "1.0.3"]
            [lein-figwheel "0.1.3-SNAPSHOT"]] ;; <- Adding also a lein plugin for it

  ;; Figwheel settings. We are going to use 'resource/public' directory as our root
  ;; directory for serving the files by the figwheel server, and also we specify
  ;; where our CSS styles live, so Figwheel could reload them without reloading the
  ;; page when we change them as well!
  :figwheel {
    :http-server-root "public"
    :port 3449
    :css-dirs ["resources/public/css"]}

  :cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src/perfection" "src/figwheel"] ;; <- adding source path where
                                                              ;;    figwheel code will live
              :compiler {
                :output-to "resources/public/perfection.js"
                :output-dir "resources/public/out"
                :optimizations :none
                :source-map true}}]})

We will place the client code for figwheel not in src/perfection, but in a separate dir, because we don’t want other builds (like ‘test’ or ‘release’) to include it.

Now, create the file src/figwheel/perfecton_figwheel.cljs, and put this code there:

1
2
3
4
(ns perfection-figwheel
  (:require [figwheel.client :as fw :include-macros true]))

(fw/watch-and-reload)

And require it from resources/public/index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
  <head>
    <title>Perfection - perfect dev environment for ClojureScript</title>
    <link href="css/style.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <script src="out/goog/base.js" type="text/javascript"></script>
    <script src="perfection.js" type="text/javascript"></script>
    <script type="text/javascript">goog.require("perfection.core");</script>
    <!-- Requiring figwheel -->
    <script type="text/javascript">goog.require("perfection_figwheel");</script>
  </body>
</html>

After that, let’s shut down our python server, but run

1
$ lein figwheel dev

instead, then open localhost:3449/index.html in the browser, and check the console. It should look like:

1
2
3
Hello world!
Figwheel: trying to open cljs reload socket
Figwheel: socket connection established

Now go to src/perfection/core.cljs, and change it to something else, e.g. change “Hello World!” to “Hello World 2!”. You should see in the console, that we reloaded our files, and the new “Hello World 2!” output:

1
2
3
4
Figwheel: loading files
Hello world 2!
Figwheel: loaded these files
("/out/perfection/core.js")

Figwheel will also track the changes in CSS and also reload them. Change the background in the CSS file from silver to blue (resources/public/css/style.css file):

1
2
3
body {
  background: blue;
}

and you’ll see in the browser console:

1
2
Figwheel: loaded CSS files
("/css/style.css")

Tests

I like unit tests. They give me confidence. But it really sucks to manually run them every time, and it especially sucks to run them in Clojure/ClojureScript, because you have to wait for the full JVM start, which takes several seconds. Not really perfect instant experience we are looking for. Thankfully, you can run it once, and then restart the tests on every change of the source or test files, via the same ‘lein clsbuild auto’ mechanism.

First thing, make sure you have PhantomJS installed. If not, install it with HomeBrew:

1
$ brew install phantomjs

Then, let’s add the ‘test’ build for cljsbuild, in project.clj:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(defproject perfection "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojurescript "0.0-2277"]
                 [figwheel "0.1.3-SNAPSHOT"]])

  :jvm-opts ["-Xmx1G"]

  :plugins [[lein-cljsbuild "1.0.3"]
            [lein-figwheel "0.1.3-SNAPSHOT"]
            [com.cemerick/clojurescript.test "0.3.1"]] ;; <- Adding lein plugin for tests

  :figwheel {
    :http-server-root "public"
    :port 3449
    :css-dirs ["resources/public/css"]}

  :cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src/perfection" "src/figwheel"]
              :compiler {
                :output-to "resources/public/perfection.js"
                :output-dir "resources/public/out"
                :optimizations :none
                :source-map true}}
             ;; Adding the new build 'test'. Note the :notify-command setting,
             ;; it will run the tests when the files change
             {:id "test"
              :source-paths ["src/perfection" "test"]
              :notify-command ["phantomjs" :cljs.test/runner "perfection_test.js"]
              :compiler {
                :output-to "perfection_test.js"
                :optimizations :whitespace}}]})

Now, let’s add some function to test (src/perfection/core.cljs):

1
2
3
4
5
6
7
(ns perfection.core)

(.log js/console "Hello world 2!")

;; Our new function
(defn add [a b]
  (+ a b))

Then, remove the currently existing test/perfection/core_test.clj, and create a new file test/perfection/core_test.cljs, which will contain our new first failing test:

1
2
3
4
5
6
7
(ns perfection.core-test
  (:require-macros [cemerick.cljs.test :refer (is deftest)])
  (:require [cemerick.cljs.test :as test]
            [perfection.core :as pn]))

(deftest ex1
  (is (= (pn/add 1 2) 4)))

Let’s try to run it. It should fail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ lein cljsbuild auto test
Compiling ClojureScript.
Compiling "perfection_test.js" from ["src/perfection" "test"]...
SyntaxError: Parse error


Testing perfection.core-test

FAIL in (perfection.core-test/ex1) (:)
expected: (= (pn/add 1 2) 4)
  actual: (not (= 3 4))

Ran 1 tests containing 1 assertions.
Testing complete: 1 failures, 0 errors.

Ran 1 tests containing 1 assertions.
Testing complete: 1 failures, 0 errors.
Successfully compiled "perfection_test.js" in 4.203 seconds.

Change (is (= (pn/add 1 2) 4)) to (is (= (pn/add 1 2) 3)), then save the file, and the test should rerun automatically, and now it should pass:

1
2
3
4
5
6
7
8
9
10
11
12
Compiling "perfection_test.js" from ["src/perfection" "test"]...
SyntaxError: Parse error


Testing perfection.core-test

Ran 1 tests containing 1 assertions.
Testing complete: 0 failures, 0 errors.

Ran 1 tests containing 1 assertions.
Testing complete: 0 failures, 0 errors.
Successfully compiled "perfection_test.js" in 0.719 seconds.

W00t! Now we have an autorunning test in our app.

Some notes: PhantomJS is pretty old, it lacks a lot of features existing in modern browsers, like Function.bind or requestAnimationFrame. So, for tests you may need shims, you can write them in separate JS files, and then add to :notify-command in project.clj for the “test” build, e.g. like that:

1
2
3
4
5
:notify-command ["phantomjs"
                 :cljs.test/runner
                 "resources/public/js/function-bind-shim.js"
                 "resources/public/js/request-animation-frame-shim.js"
                 "perfection_test.js"]

Integration with Vim

We already have a lot, but we are not going to stop :). We would also want to quickly search docs for the functions we use from Vim, be able to jump to definitions of the functions, and even execute some code right in the browser’s context, right in the context of our running application, straight from Vim. Oh yes, that would be super cool. And that is totally possible, all thanks to famous Vim master Tim Pope and his beautiful ‘vim-fireplace’ plugin.

So, let’s first configure our app so it could accept connections from repl. We need to install brepl to it. As usual, we first need to add some changes to project.clj:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
(defproject perfection "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojurescript "0.0-2277"]
                 [figwheel "0.1.3-SNAPSHOT"]])

  :jvm-opts ["-Xmx1G"]

  :plugins [[lein-cljsbuild "1.0.3"]
            [lein-figwheel "0.1.3-SNAPSHOT"]
            [com.cemerick/clojurescript.test "0.3.1"]
            [com.cemerick/austin "0.1.4"]] ;; <- Adds nice support for ClojureScript REPL.
                                           ;;    Also has Piggieback as a dependency, which
                                           ;;    we need for vim-fireplace

  :figwheel {
    :http-server-root "public"
    :port 3449
    :css-dirs ["resources/public/css"]}

  :cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src/perfection" "src/figwheel" "src/brepl"]  ;; <- adding source path
                                                                           ;;    where brepl code
                                                                           ;;    will live
              :compiler {
                :output-to "resources/public/perfection.js"
                :output-dir "resources/public/out"
                :optimizations :none
                :source-map true}}
             {:id "test"
              :source-paths ["src/perfection" "test"]
              :notify-command ["phantomjs" :cljs.test/runner "perfection_test.js"]
              :compiler {
                :output-to "perfection_test.js"
                :optimizations :whitespace}}]})

Same thing as for figwheel – we will place the client code for brepl not in src/perfection, but in a separate dir, because we don’t want other builds (like ‘test’ or ‘release’) to include it.

Now, create src/brepl/perfection_brepl.cljs:

1
2
3
4
(ns perfection-brepl
  (:require [clojure.browser.repl :as repl]))

(repl/connect "http://localhost:9000/repl")

And add its requiring to resources/public/index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
  <head>
    <title>Perfection - perfect dev environment for ClojureScript</title>
    <link href="css/style.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <script src="out/goog/base.js" type="text/javascript"></script>
    <script src="perfection.js" type="text/javascript"></script>
    <script type="text/javascript">goog.require("perfection.core");</script>
    <script type="text/javascript">goog.require("perfection_figwheel");</script>
    <!-- Requiring brepl -->
    <script type="text/javascript">goog.require("perfection_brepl");</script>
  </body>
</html>

After that, restart lein figwheel dev, then run lein repl.

Now, it’s time to install the vim-fireplace plugin for Vim. I use pathogen, so I just simply put the plugin to ~/.vim/bundle/vim-fireplace.

Next, restart Vim, open src/perfection/core.cljs, and in Vim run :Piggieback 9000. It will hang for several seconds.

Then reload localhost:3449/index.html in browser.

You may see the exception in the browser console, like:

1
2
3
Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement':
Blocked a frame with origin "http://localhost:3449" from accessing a frame with origin
"http://localhost:9000". Protocols, domains, and ports must match.

but that’s totally fine, and will work anyway.

Go back to src/perfection/core.cljs in Vim, and try to run something like :Eval (js/alert 123). The alert window should appear right in the browser. Cool, huh?

You can do a lot of things with vim-fireplace, just check the docs. It converts Vim to a pretty solid ClojureScript IDE, which now is quite close even to LightTable, but with all that well-known Vim stuff you and me love so much :)

Release

Now we have everything we need for comfortable development process, but I highly recommend that you also add another build, which will generate the “release” version of the app, which will eventually go to production. It is very important to do that right from the beginning, because sometimes there are issues with “advanced” optimizations of Google Closure (which ClojureScript uses under the hood for compilation). You should really always check that your app works with :advanced optimizations, because debugging may be difficult there.

So, add this to your project.clj:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
(defproject perfection "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojurescript "0.0-2277"]
                 [figwheel "0.1.3-SNAPSHOT"]])

  :jvm-opts ["-Xmx1G"]

  :plugins [[lein-cljsbuild "1.0.3"]
            [lein-figwheel "0.1.3-SNAPSHOT"]
            [com.cemerick/clojurescript.test "0.3.1"]
            [com.cemerick/austin "0.1.4"]]

  :figwheel {
    :http-server-root "public"
    :port 3449
    :css-dirs ["resources/public/css"]}

  :cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src/perfection" "src/figwheel" "src/brepl"]
              :compiler {
                :output-to "resources/public/perfection.js"
                :output-dir "resources/public/out"
                :optimizations :none
                :source-map true}}
             {:id "test"
              :source-paths ["src/perfection" "test"]
              :notify-command ["phantomjs" :cljs.test/runner "perfection_test.js"]
              :compiler {
                :output-to "perfection_test.js"
                :optimizations :whitespace}}
             ;; Our release build. The main difference - it uses :optimizations :advanced.
             ;; It also doesn't include src/figwheel or src/brepl - we don't need that in production
             {:id "release"
              :source-paths ["src/perfection"]
              :compiler {
                :output-to "resources/public/perfection_prod.js"
                :output-dir "resources/public/prod-out"
                :optimizations :advanced
                :pretty-print false
                :source-map "resources/public/perfection_prod.js.map"}}]})

Now create resources/public/index_prod.html, it’s going to be way shorter than our dev one:

1
2
3
4
5
6
7
8
9
<html>
  <head>
    <title>Perfection - perfect dev environment for ClojureScript</title>
    <link href="css/style.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <script src="perfection_prod.js" type="text/javascript"></script>
  </body>
</html>

And run:

1
$ lein cljsbuild auto release

Now you can open http://localhost:3449/index_prod.html, and make sure the site still works (by checking for “Hello World 2!” in the console.

BTW, now if you open resources/public/perfection_prod.js, all you will see there is:

1
2
3
4
5
;(function(){
console.log("Hello world 2!");
})();

//# sourceMappingURL=perfection_prod.js.map

That’s the power of :advanced optimizations. It got rid of everything which is not used (all the ClojureScript libs, Google Closure libs, thousands and thousands of lines of code), and left only what matters.

Summary

So, I usually have 4 things running simultaneously:

1
2
3
4
lein repl
lein figwheel dev
lein cljsbuild auto test
lein cljsbuild auto release

And whenever I make a change in the source code, I’ll get updated version of the app in the browser, without page refresh, the tests will run, and the release will be built. All automagically.

That’s what I call “the perfect dev environment”.

Tips & Tricks

Paredit

You should use it. Since Clojure is Lisp, and consists of s-expressions, it has a very clear structure. Paredit allows to efficiently edit your code by manipulating s-expressions. This is when all these parentheses, which initially scare Lisp newcomers, will bring power. There is a plugin for Vim, a bit buggy, especially when you copy/paste large structures (especially when they are unbalanced), but it’s still worth it.

p and b

I found it very useful for my development process to create the function p and the macro b for debugging.

  • p just prints what the value it receives and returns it.
  • b prints what time did the execution of the nested s-expression take, and also returns the value.

They look like this:

1
2
3
4
(defn p "Prints given arguments, and then returns the last one"
  [& values]
  (.log js/console (apply pr-str values))
  (last values))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(defn benchmark
  "Prints the execution time for the given function. Accepts optional string, which will
   be used as a description"
  ([f] (benchmark nil f))
  ([msg f]
  (let [start (.now js/Date)
        result (f)]
    (p (str (when msg (str msg ": ")) (- (.now js/Date) start) "ms"))
    result)))

(defmacro b
  ([f]
  `(tixi.utils/benchmark (fn [] ~f)))

  ([msg f]
  `(tixi.utils/benchmark ~msg (fn [] ~f))))

And then we can use them, like that. Imagine you have a function:

1
2
3
(let [foo 2
      bar (+ 3 foo)]
  (+ foo bar))

And you are curious about what’s the output of (+ 3 foo). So you can do:

1
2
3
(let [foo 2
      bar (p (+ 3 foo))]
  (+ foo bar))

And it will put ‘5’ in the browser console. Curious how long it takes to execute it?

1
2
3
(let [foo 2
      bar (b (+ 3 foo))]
  (+ foo bar))

‘0ms’ (obviously :)) will appear in the browser console.

It starts to really shine especially with Paredit, when you can wrap and unwrap parentheses by one keystroke.

Video

This is a little screencast I recorded how I develop Textik, using the dev environment like that:

Conclusion

I really think ClojureScript so far is the most interesting and productive way to build complex front-end projects on the web. Especially if you use it with React (and some React wrapper, like Om, Reagent or Quiescent). If you didn’t try it yet, you should definitely try, and then hopefully you’ll find this article useful :)

Comments