Using the Figwheel REPL within NRepl

Andrea Tupini edited this page Dec 20, 2016 · 88 revisions

Using Figwheel within nREPL

nREPL is a Clojure network REPL that provides a Clojure REPL server and client, along with some common APIs of use to IDEs and other tools that may need to evaluate Clojure code in remote environments.

The current default for connecting to a Clojure process remotely is nREPL. This is likely to change in the near future with the new Socket REPL support in Clojure 1.8. Until then, nREPL continues to be the standard.

In the past it has been very challenging to get a ClojureScript/Figwheel REPL up and running over a nREPL connection. This has changed recently as new versions of leiningen and Figwheel have enabled this to work with much less configuration.

Even though this has recently become easier, you should still consider setting up a workflow that includes nREPL as an advanced undertaking as it requires a lot of contextual knowledge of the Clojure environment if something goes wrong.

It's important to note that when you run lein repl it defaults to an nREPL based session.

New Abilities

The lein figwheel is capable of launching an nREPL server that your tooling can connect to by using the :nrepl-port configuration parameter. This is not the strategy recommended below.

I am now recommending that you forgo using lein figwheel and instead run lein repl and then launch figwheel from the Clojure nREPL session. This will allow you to reuse all of your project.clj's nrepl configuration.

Requirements

leiningen 2.5.3 or later and figwheel-sidecar 0.5.0 or later.

First the classpath

If you don't use lein cljsbuild or lein figwheel to start your compile process, it is quite likely that your classpath is missing your ClojureScript source paths.

A typical project.clj for a figwheel project:

(defproject repler "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [org.clojure/clojurescript "1.7.170"]]

  :plugins [[lein-figwheel "0.5.0-1"]]

  :source-paths ["src"] ;; <--- Note classpath

  :clean-targets ^{:protect false} ["resources/public/js" :target]

  :cljsbuild {:builds
              [{:id "dev"
                :source-paths ["cljs_src"] ;;<--- note this isn't in source-paths above
                :figwheel true
                :compiler {:main repler.core
                           :asset-path "js/out"
                           :output-to "resources/public/js/repler.js"
                           :output-dir "resources/public/js/out" }}]})

When you run lein figwheel the cljs_src directory is added to :source-paths (and eventually added to the classpath) of your environment. This is needed if you have any .clj or .cljc files in your cljs source directories. When you are not using lein figwheel or lein cljbuild but starting figwheel from lein repl, as we will be below, your classpath might not be correct.

We can fix this by either setting the root level :source-paths like this:

(defproject repler "0.1.0-SNAPSHOT"
  ...
  :source-paths ["src" "cljs_src"] ;; <--- Note classpath
  ...
 )

or use lein profile merging to alter the :source-paths.

(defproject repler "0.1.0-SNAPSHOT"
  ...
  :source-paths ["src"] 
  :profiles {:dev {:source-paths ["cljs_src"]}}
  ...
 )

The way chosen to fix the classpath to include your ClojureScript source paths is dependent on your deployment goals and how you are sharing code within your project.

Add figwheel-sidecar as a dependency

In order to access figwheel from the Clojure REPL, we need to add figwheel-sidecar as a development time dependency:

(defproject repler "0.1.0-SNAPSHOT"
  ...
  :source-paths ["src"] 
  :profiles {:dev {:dependencies [[figwheel-sidecar "0.5.8"]]
                   :source-paths ["cljs_src"] }}
  ...
 )

Launching Figwheel from nREPL

Let's test and see that you can launch a figwheel process in nREPL.

In the root of the project directory (where project.clj is located) start nREPL:

lein repl

This should launch a Clojure nREPL session. Now at the prompt type this:

user> (use 'figwheel-sidecar.repl-api)
nil
user> (start-figwheel!)
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
Compiling "resources/public/js/repler.js" from ["src"]...
Successfully compiled "resources/public/js/repler.js" in 2.06 seconds.
#<SystemMap>
user> 

You have successfully started figwheel within an nREPL session!

An important note about configuration: The (start-figwheel!) call will automatically pull your configuration from the project.clj BUT ... no leiningen profile merging will occur (this is because we are reading the config raw from the project.clj file).

You can pass a configuration straight into the figwheel-sidecar.repl-api/start-figwheel! function if you prefer:

user> (def figwheel-config
        {:figwheel-options {} ;; <-- figwheel server config goes here 
         :build-ids ["dev"]   ;; <-- a vector of build ids to start autobuilding
         :all-builds          ;; <-- supply your build configs here
           [{:id "dev"
             :figwheel true
             :source-paths ["cljs-src"]
             :compiler {:main "repler.core"
                        :asset-path "js/out"
                        :output-to "resources/public/js/repler.js"
                        :output-dir "resources/public/js/out" }}]})
user> (start-figwheel! figwheel-config)
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
Compiling "resources/public/js/repler.js" from ["src"]...
Successfully compiled "resources/public/js/repler.js" in 2.06 seconds.
#<SystemMap>

Now that we can configure and launch the figwheel autobuilding process from nREPL let's look at launching the Figwheel REPL.

Launching the figwheel CLJS REPL

We are not ready to launch the figwheel CLJS REPL yet. In order for a CLJS REPL to work over an nREPL connection we will need to install nREPL middleware that intercepts the nREPL input messages and evals them in a ClojureScript REPL.

For that, we need to add piggieback nREPL middleware to our project.clj as dependency and nrepl-middleware:

(defproject repler "0.1.0-SNAPSHOT"
  ...
  :source-paths ["src"] 
  :profiles {:dev {:dependencies [[com.cemerick/piggieback "0.2.1"] <-- Note
                                  [figwheel-sidecar "0.5.8"]]
                   :source-paths ["cljs_src"] }}
  :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} <-- Note
  ...
 )

Start Figwheel REPL within nREPL

Now we have everything we need to use the Figwheel REPL from nREPL.

To verify that this is working launch nREPL again from lein:

lein repl

and at the prompt try this:

user> (use 'figwheel-sidecar.repl-api)
nil
user> (start-figwheel!)
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
Compiling "resources/public/js/repler.js" from ["src"]...
Successfully compiled "resources/public/js/repler.js" in 2.06 seconds.
#<SystemMap>
user> (cljs-repl)
Launching ClojureScript REPL for build: dev
Figwheel Controls:
          (stop-autobuild)                ;; stops Figwheel autobuilder
          (start-autobuild [id ...])      ;; starts autobuilder focused on optional ids
          (switch-to-build id ...)        ;; switches autobuilder to different build
          (reset-autobuild)               ;; stops, cleans, and starts autobuilder
          (reload-config)                 ;; reloads build config and resets autobuild
          (build-once [id ...])           ;; builds source one time
          (clean-builds [id ..])          ;; deletes compiled cljs target files
          (print-config [id ...])         ;; prints out build configurations
          (fig-status)                    ;; displays current state of system
  Switch REPL build focus:
          :cljs/quit                      ;; allows you to switch REPL to another build
    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
cljs.user>

Note that you will have to have figwheel setup correctly and will have to open your application in the browser before the cljs.user> prompt will appear.

Please remember that using figwheel within nREPL is still considered advanced level setup. If you are still new to Figwheel and CLJS development, this is probably not the place to start.

Figwheel system api available in nREPL

It is interesting to note that all the Figwheel system control functions that are available in the Figwheel REPL are also available in the figwheel-sidecar.repl-api namespace.

You can see the docs for the api in nREPL like so (you must be back at the nREPL prompt):

user> (figwheel-sidecar.repl-api/api-help)

The result will be:

-------------------------
figwheel-sidecar.repl-api/cljs-repl
([] [id])
  Starts a Figwheel ClojureScript REPL for the provided build id (or
the first default id).
-------------------------
figwheel-sidecar.repl-api/fig-status
([])
  Display the current status of the running Figwheel system.
-------------------------
figwheel-sidecar.repl-api/start-autobuild
([& ids])
  Starts a Figwheel autobuild process for the builds associated with
the provided ids (or the current default ids).
-------------------------
figwheel-sidecar.repl-api/stop-autobuild
([& ids])
  Stops the currently running autobuild process.
-------------------------
figwheel-sidecar.repl-api/build-once
([& ids])
  Compiles the builds with the provided build ids
(or the current default ids) once.
-------------------------
figwheel-sidecar.repl-api/clean-builds
([& ids])
  Deletes the compiled artifacts for the builds with the provided
build ids (or the current default ids).
-------------------------
figwheel-sidecar.repl-api/switch-to-build
([& ids])
  Stops the currently running autobuilder and starts building the
builds with the provided ids.
-------------------------
figwheel-sidecar.repl-api/reset-autobuild
([])
  Stops the currently running autobuilder, cleans the current builds,
and starts building the default builds again.
-------------------------
figwheel-sidecar.repl-api/reload-config
([])
  Reloads the build config, and resets the autobuild.
-------------------------
figwheel-sidecar.repl-api/api-help
([])
  Print out help for the Figwheel REPL api

So all these functions are available within the Clojure nREPL for your convenience.

Helper functions in user.clj

When you start a Clojure process, it looks for a user.clj on your classpath and loads it. You can use this to have some helper functions available to you when you connect to your nREPL environment.

First, let's add the "dev" source path that is only available at development time:

(defproject repler "0.1.0-SNAPSHOT"
  ...
  :source-paths ["src"] 
  :profiles {:dev {:dependencies [[com.cemerick/piggieback "0.2.1"]
                                  [figwheel-sidecar "0.5.8"]]
                   :source-paths ["cljs_src" "dev"] }} ;; <-- Note the addition of "dev"
  :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
  ...
 )

Now all we need to do is create dev/user.clj:

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

(defn start [] (ra/start-figwheel!))

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

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

After this, when you run lein repl, the user.clj file will be loaded and you will have access to all the functions that you have defined in there.

Integration with Emacs/CIDER

Super Advanced

Once you have nREPL working, you may want integrate this setup with Emacs via CIDER. CIDER is the Clojure Interactive Development Environment that Rocks.

Please see the excellent Brave and True guide on setting up Emacs for Clojure

Leiningen

First, in order for this to work in emacs, you need to already have clojure-mode installed.

Then install the latest stable CIDER Emacs package:

First add the following to your ~/.emacs.el or ~/.emacs.d/init.el file, to ensure you're using MELPA Stable

(add-to-list 'package-archives 
   '("melpa-stable" . "http://melpa-stable.milkbox.net/packages/") t)
(package-initialize) ; Make sure you don't run this more than once!

After that, install the CIDER package:

M-x package-install [RET] cider [RET]

This should install the latest stable CIDER version, which is 0.13.0

You'll first want to make sure that you've got piggieback and figwheel-sidecar dependencies in your project.clj:

(defproject repler "0.1.0-SNAPSHOT"
  ...
  :source-paths ["src"] 
  :profiles {:dev {:dependencies [[com.cemerick/piggieback "0.2.1"]
                                  [figwheel-sidecar "0.5.8"]]}}
  ...
 )

Next, you need to change how CIDER starts a cljs-lein-repl. This can be done by editing your ~/.emacs.el or ~/.emacs.d/init.el file:

;; ~/.emacs.el or ~/.emacs.d/init.el

(require 'cider)
(setq cider-cljs-lein-repl
      "(do (require 'figwheel-sidecar.repl-api)
           (figwheel-sidecar.repl-api/start-figwheel!)
           (figwheel-sidecar.repl-api/cljs-repl))")

Now in Emacs, just put your cursor in a .clj or .cljs buffer that is part of the project you are configuring.

Once there, type M-x cider-jack-in-clojurescript [RET]

If this all succeeds, you will see a Clojure REPL in an Emacs buffer and another Figwheel REPL side-by-side.

Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
Compiling "resources/public/js/repler.js" from ["src"]...
Successfully compiled "resources/public/js/repler.js" in 2.06 seconds.
#<SystemMap>
Launching ClojureScript REPL for build: dev
Figwheel Controls:
          (stop-autobuild)                ;; stops Figwheel autobuilder
          (start-autobuild [id ...])      ;; starts autobuilder focused on optional ids
          (switch-to-build id ...)        ;; switches autobuilder to different build
          (reset-autobuild)               ;; stops, cleans, and starts autobuilder
          (reload-config)                 ;; reloads build config and resets autobuild
          (build-once [id ...])           ;; builds source one time
          (clean-builds [id ..])          ;; deletes compiled cljs target files
          (print-config [id ...])         ;; prints out build configurations
          (fig-status)                    ;; displays current state of system
  Switch REPL build focus:
          :cljs/quit                      ;; allows you to switch REPL to another build
    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
cljs.user>

Now with your cursor in a .cljs file that is part of your project you can use C-x C-e to evaluate the form preceding point and display the result in the echo area.

Optional Emacs/CIDER keybinding

Alternatively, instead of setting cider-cljs-lein-repl and using cider-jack-in-clojurescript, you can just use cider-jack-in and add the following function and binding to your Emacs config:

(defun cider-figwheel-repl ()
  (interactive)
  (save-some-buffers)
  (with-current-buffer (cider-current-repl-buffer)
    (goto-char (point-max))
    (insert "(require 'figwheel-sidecar.repl-api)
             (figwheel-sidecar.repl-api/start-figwheel!) ; idempotent
             (figwheel-sidecar.repl-api/cljs-repl)")
    (cider-repl-return)))

(global-set-key (kbd "C-c C-f") #'cider-figwheel-repl)

Additionally there are times where you want to send block directly to the browser (as opposed to evaluating them in Emacs).

(defun user/cider-send-to-repl ()
  (interactive)
  (let ((s (buffer-substring-no-properties
            (nth 0 (cider-last-sexp 'bounds))
            (nth 1 (cider-last-sexp 'bounds)))))
    (with-current-buffer (cider-current-connection)
      (insert s)
      (cider-repl-return))))

Integration with Vim

https://github.com/bhauman/lein-figwheel/wiki/Using-the-Figwheel-REPL-with-Vim

The old way of connecting to nREPL:

Everything below here refers to a deprecated way of connecting to a process running Figwheel with nREPL.

I feel this is vastly inferior to the method described above but it has the advantage that you can get everything up and running quickly by simply configuring your project.clj and running lein figwheel.

This way of connecting to nREPL will be deprecated in the future.

Connecting to the Figwheel process started by lein figwheel

Figwheel can launch an nREPL server so that you can remotely connect to the Clojure process in which it is running.

To have figwheel launch an nREPL server you will need to add the :nrepl-port option to the :figwheel config in your project.clj

:figwheel {
 ;; Start an nREPL server into the running figwheel process
 :nrepl-port 7888
}

Cider middleware

You can configure the middleware to load by adding the :nrepl-middleware option to the :figwheel config in project.clj

:figwheel {
  ;; Start an nREPL server into the running figwheel process
  :nrepl-port 7888

  ;; Load CIDER, refactor-nrepl and piggieback middleware
  :nrepl-middleware ["cider.nrepl/cider-middleware"
                     "refactor-nrepl.middleware/wrap-refactor"
                     "cemerick.piggieback/wrap-cljs-repl"]
}

You will need to make sure that the specified middleware is available on your classpath. So make sure its included in :depedencies, :plugins etc of your project.clj.

Specifying :nrepl-middleware will override the default inclusion of Piggieback middleware so make sure you add the Piggieback middleware as well.

IF you want to run the nREPL without any middleware you can just provide an empty vector.

:nrepl-middleware []

Now when you start figwheel lein figwheel the nREPL server will be started with your preferred middleware.

Piggieback, nREPL support for the CLJS REPL

If you want to use the CLJS REPL over an nREPL connection you are going to need Piggieback

As of version 0.4.0 figwheel no longer has a hard dependency on Piggieback. It will still try to load the Piggieback repl when you have an nREPL connection open, but if it isn't available it will start the default cljs repl.

If you want to use Piggieback you'll need to add the dependency to your project yourself.

Note: because of the changes, figwheel no longer needs Piggieback 0.1.5. You can use either Piggieback 0.1.5 or 0.2.1+.

Example: [com.cemerick/piggieback "0.2.1"]

Please see the piggieback readme

Starting a Figwheel ClojureScript REPL from nREPL

Once you are connected to an nREPL server in the Figwheel process with Piggieback middleware, you can start the Figwheel ClojureScript REPL like this:

(use 'figwheel-sidecar.repl-api)
(cljs-repl)

Again, this will only work if you are connected to a process where the Fighweel server code is running.

The above incantation will also work if you are not using nREPL.