I don’t give a fig(wheel) – part 2

In the first part of this blog I showed a way of loading figwheel from an nREPL while keeping the figwheel code separate from the generic build used to build for production.

One of the things I wanted to ensure in my lein project was that any Clojurescript test code is kept separate from the production code to be shipped.

I decided to use the doo library to run my cljs tests. I experimented with using the cljsbuild plugin with a js script and phantomjs to run cljs tests as described here but I found this approach didn’t always report failures in async tests correctly.

To set up doo you need to add the following entries to the project.clj file:

 :profiles {
   :dev {
     ...
     :source-paths ["dev/src" "src-cljs" "test-cljs"] ; add test dir
     :dependencies [...
                    [lein-doo "0.1.6"]]
     :plugins [[lein-doo "0.1.6"]]}}

As well as adding lein-doo to the dependencies and the plugins for only the :dev profile, you need to add the directory containing the test code to the source paths. In my case I am keeping my test code in a directory  called ‘test-cljs’.

In addition lein-doo needs a test runner to bootstrap it. Therefore I added a test-runner namespace that calls the doo-tests function to run the test namespaces (in this case only core-test).

(ns blogcljsfigwheel.test-runner
 (:require [doo.runner :refer-macros [doo-tests]]
 [blogcljsfigwheel.core-test]))
(enable-console-print!)
(doo-tests 'blogcljsfigwheel.core-test)

I also needed to add the cljsbuild config for the test namespaces to the project file to ensure the test code is transpiled to JavaScript.

:cljsbuild {:builds {...
                     :test {:source-paths ["src-cljs" "test-cljs"]
                            :compiler {:main "blogcljsfigwheel.test-runner"
                                       :output-to "resources/private/js/unit-test.js"
                                       :optimizations :whitespace
                                       :pretty-print true}}}}

If you have phantomjs installed on your path you can then run the tests using lein doo phantom test.

As you can see the test build outputs to the resources/private directory whereas I configured the app build to output to the target directory:

 :cljsbuild {:builds 
              {:app 
                {:source-paths ["src-cljs"]
                 :compiler {:main "blogcljsfigwheel.core"
                            :output-to "target/cljsbuild/public/js/compiled/blogcljsfigwheel.js"
                            :output-dir "target/cljsbuild/public/js/compiled/out"
                            :asset-path "js/compiled/out"}}
              ...
              }

In order to package the Clojurescript in the uberjar I also needed to add:

 :profiles {:uberjar {:aot :all
                      :cljsbuild 
                        {:builds 
                          {:app 
                            {:jar true
                             :compiler {:optimizations :advanced}}}}
                      :prep-tasks ["compile" ["cljsbuild" "once" "app"]]}
   ...
   }

The prep-tasks ensure that the cljsbuild is run for the app build when the uberjar is built using lein uberjar.

 

 

I don’t give a fig(wheel)

This blog is actually more of a collection of notes to myself for how to configure Leiningen to build a Clojure server and Clojurescript client.

Over the last few weeks I’ve been trying to work out how to build a Clojurescript client and make sure test code is not included in the production build and that Figwheel is included only in the dev build.

In the past the approved way to connect to the Figwheel REPL using nREPL was to add :nrepl-port configuration parameter to your project.clj . You would then start Figwheel using the lein figwheel plugin, which would then allow your favourite editor to connect to Figwheel using nREPL.

The new way to connect is detailed here. However, I have struggled with configuring the profile.clj file to share some cljsbuild parameters across profiles but still inject figwheel into the build for development only using this new approach.

What follows is a guide to how I accomplished this although I am sure there are other approaches.

Setting up common cljsbuild

I wanted one place to configure the common parts of the cljsbuild so I started by adding an ‘app’ build.

 (defproject blogcljsfigwheel "0.1.0-SNAPSHOT"
 ...
 :resource-paths ["resources" "target/cljsbuild"]
 ...
 :cljsbuild {
   :plugins [[lein-cljsbuild "1.1.1"]
             [lein-figwheel "0.5.1"]] ;; Note figwheel plugin
   :builds
     {:app {:source-paths ["src-cljs"]
            :compiler 
              {:main "blogcljsfigwheel.core"
               :output-to "target/cljsbuild/public/js/compiled/blogcljsfigwheel.js"
               :output-dir "target/cljsbuild/public/js/compiled/out"
               :asset-path "js/compiled/out"}}}...
 :profiles {:dev
            {:dependencies [[figwheel-sidecar "0.5.1"] ; Deps for sidecar 
                            [com.cemerick/piggieback "0.2.1"]] ; piggieback
 :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]})

I initially thought that I could add the figwheel true option to the :dev profile. Like so:

:profiles {
          :dev 
            {:cljsbuild 
              {:builds  
                {:app {:figwheel true}}
             :dependencies ...}} 

And then launch figwheel by starting a REPL (either a lein repl or a CIDER repl) and then connect by starting Figwheel from the repl. Like so:

$ lein repl
user=> (use 'figwheel-sidecar.repl-api)
nil
user=> (do (start-figwheel!) nil)
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - app
Compiling "target/cljsbuild/public/js/compiled/blogcljsfigwheel.js" from ["src-cljs"]...
Successfully compiled "target/cljsbuild/public/js/compiled/blogcljsfigwheel.js" in 1.057 seconds.
nil
user=> (cljs-repl)
Launching ClojureScript REPL for build: app
Figwheel Controls:
          ....
    Docs: (doc function-name-here)
    Exit: Control+C or :cljs/quit
 Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application

However (start-figwheel!), although it pulls in the cljsbuild config from profiles.clj, doesn’t merge in profiles.

So to get around this I created a user.clj file that I added some helper functions and a custom figwheel config to.

 
(ns user
  (:require [figwheel-sidecar.repl-api :as ra]))

;; As figwheel doesn't seem to properly merge cljsbuild config from profiles
;; we have to define our figwheel config here and use some helper fns to start
;; and stop figwheel.

(def figwheel-config
  {:figwheel-options {}
   :build-ids ["dev"]
   :all-builds
   [{:id "dev"
     :source-paths ["src-cljs"]
     :figwheel true
     :compiler {:main "blogcljsfigwheel.core"
                :output-to "target/cljsbuild/public/js/
                            compiled/blogcljsfigwheel.js"
                :output-dir "target/cljsbuild/public/js/compiled/out"
                :asset-path "js/compiled/out"}}]})


(defn start-figwheel! [] (do (ra/start-figwheel! figwheel-config) nil))

(defn stop-figwheel! [] (do (ra/stop-figwheel!) nil))

(defn cljs-repl [] (ra/cljs-repl))

I put this user.clj file in a dev/src directory and then added this directory to the :dev source paths.

:profiles {
   :dev {...
         :source-paths ["dev/src" "src-cljs"]
         ...
        }...
    } 

I also added user as the initial namespace for the repl-options.

:repl-options {:init-ns user ...}

Once this is in place you can start a REPL and issue the commands:

user=> (start-figwheel!)
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
Compiling "target/cljsbuild/public/js/compiled/blogcljsfigwheel.js" from ["src-cljs"]...
Successfully compiled "target/cljsbuild/public/js/compiled/blogcljsfigwheel.js" in 2.578 seconds.
nil
user=> (cljs-repl)
Launching ClojureScript REPL for build: dev
Figwheel Controls:
          ....
    Docs: (doc function-name-here)
    Exit: Control+C or :cljs/quit
 Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application
To quit, type: :cljs/quit
nil
cljs.user=>


Note: The figwheel-sidecar.repl-api is not directly required and referred through the ‘use’ macro so the (start-figwheel!) and (cljs-repl) commands are those defined in the user.clj file that use the custom figwheel-config.

Server side

Obviously, you need the server running to serve the client .js file. In my case I served the compiled JavaScript from hiccup mapped to the root of the server using compojure.

(ns blogcljsfigwheel.http
  (:require [com.stuartsierra.component :as component]
            [compojure.core :refer [defroutes GET POST]]
            [compojure.route :as route]
            [hiccup.core :refer [html]]
            [hiccup.page :refer [include-js]]
            [ring.middleware.defaults :refer [wrap-defaults api-defaults]]
            [ring.adapter.jetty :refer [run-jetty]]))

(def home-page
  (html
   [:html
    [:head
     [:meta {:charset "utf-8"}]
     [:meta {:name "viewport"
             :content "width=device-width, initial-scale=1"}]
     [:link {:rel "stylesheet" :href "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" :integrity "sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" :crossorigin "anonymous"}]]
    [:body
     [:div#app
      [:h3 "Loading...."]
      [:p "Loading application. Please wait...."]]
     (include-js "js/compiled/blogcljsfigwheel.js")
     [:script {:type "text/javascript"} "addEventListener(\"load\", blogcljsfigwheel.core.main, false);"]]]))

(defroutes app-routes
  (route/resources "/")
  (GET "/" [] home-page)
  (POST "/echo/:echo" [echo] (str echo " has been to the server and back."))
  (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes api-defaults))
 
... Not shown - code to run server. I'm using Jetty Adapter and Stuart Sierra's components ...
 

I am using an exported ‘main’ function in a different (core) namespace to start the server but you can see the ‘addEventListener code bootstrapping the server in the script tag in home-page.

I then define helper functions in the user.clj to allow me to start, restart and stop the server using Stuart Sierra’s component reload workflow.

See my next blog for more detail or clone the repo here


Blog to follow, how to add cljs tests without polluting the uberjar build.