Trying to setup a CLJ(S) dev envirionment with WSL2

I’m trying to setup a CLJ(S) development environment for a full-stack hobby project on WSL2 on Windows 10.

Just wanted to report my experience. Maybe this is helpful for others and maybe someone has a few suggestions or can tell me what I did wrong.

Installing WSL2 was easy basically just wsl --install (see Install WSL | Microsoft Docs for more infos). I’m using Ubuntu as distro.

Then I created a project with a leiningen template with shadow-cljs for the frontend. Up to this point everything was relatively easy…

I started shadow-cljs watch and opened the browser. So far so good.

I first started all clojure tools in a podman container but due to the following troubles I dropped the container and just run everything directly on WSL. However, if you want to user containers you need to configure 3 ports (the app server, the shadow-cljs server and the nREPL). Since you need to specify the ports when starting the container they must be configured in the various configuration files since they will be random otherwise.

From here I ran into various issues.

Issue 1: shadow-cljs does not detect changes to the files in the Windows filesystem. I found some hints about a polling mode but could not get it to work.

This was easy to solve: move the code to the WSL filesystem.

Issue 2: Emacs on Windows is not able to write to the WSL filesystem. Found some suggestions about advising a function to ignore the underlying ACL issues. I also thing TRAMP could work around this. However, this would not help due to the next issue.

Issue 3: I cannot connect from Windows to a repl started in WSL or vice versa. (ConnectException Connection refused: connect)

This seems to be a general issue with nREPL in WSL and not a cider issue. Strangely other ports for the server and shadow-cljs works without issues.

This issue is the worst for me since it rules out using other windows tools effectively with the exception of VS Code.

With its remote development tools it is able to have the UI in native Windows and access the tools and the nREPL port in WSL. The Calva extension appears to be fairly good. However I was a bit
confused about the jack-in options. Somehow Calva provided the options "my-project Serve"r and “my-project Server + Client”. After some research I guess it magically detected how to start the server from the connected repl. Not sure how it knows which function to start (and what if the code does not have a
function to start the server?).

Know I tried to install Emacs in WSL (emacs-nox, elpa-cider). The shadow-cljs documentation explains how to start the watch repl. In the Cider repl I tried to start the server as Calva does it but got another error

Issue 4: “No available JS runtime.”

The shadow-cljs doc suggestions that you need a browser connected which is strange since I need the server to connect the browser. But the solution was easy. The steps in the shadow-cljs doc start a CLJS repl and you just need to drop “back” to a CLJ repl (:cljs/quit) then I could start the server the same as Calva does it with function provided by the luminius template.

So after some trouble I think I have all tools working in WLS (container should also work from here) and I can either user VS Code via remote development or terminal emacs inside WSL.

P.S. Can someone recommend a color scheme for terminal emacs in Windows terminal? The default blue on black in the minibuffer is barely readable.

1 Like

For performance reasons, you will want your projects on WSL anyway.

FWIW, I find VS Code on Windows with the Remote-WSL extension to be a really good way to work. Calva has an option to just connect to an existing, running REPL (on the WSL side) which should work just fine – many people find that a better option than figuring out the right incantations to have Calva start the REPL for you.

Aside from the jack-in vs connect considerations, there’s the issue of how often you restart your REPL vs how often you end up restarting VS Code. My REPLs run for days or weeks (or sometimes months) but I find myself needing to restart my editor a lot more often (even before I switched to VS Code) so it makes sense to me to start your REPLs manually and then just “connect” to them from your editor.

If you’re determined to stick with Emacs, you might want to consider running it on WSL either in a bash terminal window or, if you use the X11 version and your WSL is up-to-date it should have WSLg support built-in so you can run Linux GUI programs. I often use google-chrome from the Linux side.

Also, note the ClojureScript caveat about the browser auto-launching and not connecting to the REPL properly: use --repl-opts "{:launch-browser false}" when starting the REPL and then manually point your browser at the appropriate address – see ClojureScript - Quick Start

For emacs on WSL2, check out Especially the blog on gtk-enabled emacs + wslg: Using Emacs on Windows 11 with WSL2 | Emacs Redux but there are other interesting posts too.

This is unfortunately an issue with WSL2 and affects all file watcher implementations. If you look over the WSL github you’ll find many open issue (with long comment threads). Polling doesn’t exist for this case anymore.

The nREPL server by default only listens on while the other servers use You can set :nrepl {:host "" :port 12345} in your shadow-cljs.edn config, which should make nrepl accessible from windows assuming you are using the correct IP/port to connect.

Make sure you are not actually running 2 instances of shadow-cljs. If you run one directly in Windows (started by Calva/emacs) and one in WSL (started via CLI or so) then they won’t know about each other and be completely separate things. Since WSL has its own network environment the automatic port forwarding thing WSL does might not work so you might never know which instance you are actually talking to.

FWIW I’m still using WSL1 since it doesn’t run in its own VM and IMHO interop is much better. I can work on stuff from Windows (running Cursive) on the Windows Disk and still access them via the mounted path in WSL. Sure some fs ops are slower when running in WSL and working on the Windows FS but the tradeoff works for me. Much less headache not dealing with separate network interfaces, etc.

Just run everything on the WSL2 Linux distro and use a windows X-window server for GUI applications like X410 or VcXsrv. It’s much simpler I’ve found. Then you can just have linux Gnu Emacs and jack-in straight from Cider.

A bit like in this guide: Using Emacs on Windows with WSL2 | Emacs Redux

I run everything on WSL2, via VS code and Calva.

All the projects are in the WSL2 filesystem, so there is no speed penalty of the networked filesystem.

Only small thing is that my firewall blocks things that connect out. like Oz, dbs, web servers etc.
This means that I can’t connect to dbs or web servers running inside the WSL2 env from my normal windows browser without opening the firewall a bit.

Some web services (I had this problem with django/python) needs to be configured not to run on but on, because the windows machine is on a ‘outside’ network technically.

Also if you need graphical applications, you need an X server and this in your .bashrc:
export DISPLAY=$(awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null):0
which automatically sets the display to addressed to your windows ip.

so I use this powershell script to fix the firewall rules (as admin). I found it somewhere on the web a long time ago.

# bash.exe might not work on some machines.. if so, try wsl hostname -I instead... 
$remoteport = bash.exe -c "ifconfig eth0 | grep 'inet '"
$found = $remoteport -match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';

if( $found ){
  $remoteport = $matches[0];
} else{
  echo "The Script Exited, the ip address of WSL 2 cannot be found";


#All the ports you want to forward separated by coma

#[Static ip]
#You can change the addr to your ip config to listen to a specific address
$ports_a = $ports -join ",";

#Remove Firewall Exception Rules
iex "Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' ";

#adding Exception Rules for inbound and outbound Rules
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Outbound -LocalPort $ports_a -Action Allow -Protocol TCP";
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort $ports_a -Action Allow -Protocol TCP";

for( $i = 0; $i -lt $ports.length; $i++ ){
  $port = $ports[$i];
  iex "netsh interface portproxy delete v4tov4 listenport=$port listenaddress=$addr";
  iex "netsh interface portproxy add v4tov4 listenport=$port listenaddress=$addr connectport=$port connectaddress=$remoteport";

This helped but only works if I explicitly use the IP of the WSL VM on the client windows site. This is still strange since all ports in the browser work with localhost. Maybe Windows/WSL has some extra support for http ports.

Looks like starting and connecting to a REPL myself is also something I have to learn more about.

When I start the REPL via Calva or Cider (jack-in) I end up in a CLJS REPL when I start it manually via npx shadow-cljs watch and connect with cider-connect I only get a CLJ REPL…

It is quite intentional that the REPL provided by shadow-cljs starts out as a CLJ REPL. It acts as the central point to control shadow-cljs itself (eg. starting/stopping builds) and lets you switch between different CLJS REPLs. So once you are connected to the proper shadow-cljs CLJ REPL you can switch to a CLJS REPL for your build via evaling (shadow.cljs.devtools.api/repl :your-build-id). You may also switch new a blank node CLJS REPL via (shadow.cljs.devtools.api/node-repl) or a blank browser CLJS REPL (shadow.cljs.devtools.api/browser-repl). You may exit either of those via :cljs/quit and you’ll be back at the CLJ REPL.

I don’t know how any of this translates for emacs/cider commands, so someone else will have to comment on that.

I configured ssh in WSL (see SSH on Windows Subsystem for Linux (WSL) | Illuminia Studios).

Now I can access the files in Emacs via TRAMP (using plink on Windows). Cider is able to start and connect a new REPL (jack-in) when starting from a remote buffer. Or I can connect to the manually started repl via the WSL IP. I assume performance issues due to saving via TRAMP do not matter much since code changes will be made via re-evaluation in the REPL!? Cider has some issues with ssh ports other than 22. It tries to append :22 to the tramp host#port. Not sure why it needs to do this when connecting but I can live with the default port for now.

Now I can

  • start the shadow-server and repl npx shadow-cljs -d nrepl/nrepl:0.9.0-beta4 -d cider/cider-nrepl:0.27.2 server (this is what Cider does on jack-in)
  • connect with cider
  • start the backend server from the repl
  • start shadow watch from the repl
  • change the repl between CLJ and CLJS as needed

Seems like I finally have the setting I wanted: native Windows Emacs with Cider and all Clojure tools (+ Java, node.js) installed in WSL :smiley:


1 Like

In my experience, the native Windows Emacs builds are not very good, so I’ve actually been happier with Emacs in WSL, but your mileage may vary.

The only issue I have with Emacs WSL + X410 is that it doesn’t survive a computer sleep/hibernate.

Have you had any better luck with WSLg or have you not tried that?

I haven’t tried it yet, cause I’m not on the Windows 10 insider, and did not upgrade to Win 11 yet. Probably will once I get pushed the upgrade to Win11 though and can report back.

1 Like

It’s been working well for me. I was originally using VcXsrv but was happy to ditch it once WSLg became available :slight_smile:

1 Like