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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s