-
-
Notifications
You must be signed in to change notification settings - Fork 208
Using the Figwheel REPL 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.
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.
leiningen 2.5.3
or later and figwheel-sidecar 0.5.0
or later.
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.
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.16"]]
:source-paths ["cljs_src"] }}
...
)
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.
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"
:plugins [[lein-figwheel "0.5.16"]
[lein-cljsbuild "1.1.7" :exclusions [[org.clojure/clojure]]]]
:source-paths ["src"]
:cljsbuild {:builds [{:id "dev"
:source-paths ["cljs_src"]
:figwheel {:open-urls ["http://localhost:3449/index.html"]}
:compiler {:main <your-ns>.core
:asset-path "js/compiled/out"
:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/out"
:pretty-print false}}]}
:profiles {:dev {:dependencies [[cider/piggieback "0.3.10"] <-- Note
[figwheel-sidecar "0.5.16"]]
:source-paths ["cljs_src"]
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}}} <-- Note
...
)
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.
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.
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 [[cider/piggieback "0.3.10"]
[figwheel-sidecar "0.5.16"]]
:source-paths ["cljs_src" "dev"] }} ;; <-- Note the addition of "dev"
:repl-options {:nrepl-middleware [cider.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.
Advanced
The steps above for starting cljs-repl within nrepl should work. Instead of starting a separate lein repl
, start a standard cider-jack-in
command to start a standard cider nrepl. Then the following commands would start a cljs repl and evaluating cljs forms should work as expected.
(use 'figwheel-sidecar.repl-api)
(start-figwheel!)
(cljs-repl)
Steps to automate these commands can be found below, but beware they may not be always working due to backward incompatible changes by Cider releases. The above manual steps are a safe fallback.
Once you have nREPL working, you may want integrate this setup with Emacs via CIDER. CIDER. Please see the excellent Brave and True guide on setting up Emacs for Clojure.
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.18.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 [[cider/piggieback "0.3.10"]
[figwheel-sidecar "0.5.16"]]}}
...
)
See complete config above in nrepl setup.
Note: the following instructions don't work for cider 0.18 and was likely working for previous versions. Use with caution.
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.
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))))
https://github.com/bhauman/lein-figwheel/wiki/Using-the-Figwheel-REPL-with-Vim
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.
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
}
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.
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
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.