December 19, 2023

nREPL in action - n is for Network

The REPL (Read Eval Print Loop) is a pretty cool piece of technology.

I got my first taste of the REPL with python, which was really cool, having been trained in C++. All of the sudden I could query the state of my program, while it was still running. Debuggers in all their glory, but this was a new tool in the toolbox, that allowed for previous unparalled flexibility.

n is for Network

n is for Network

The real killer feature is when you add network into the mix. This allows for you to send commands to the REPL, that is running inside your active program, and update and modify your program, while it is still running, from inside your editor. This is a highly interactive type of programming, that allows insights into your program, that is difficult to achieve when your toolbox is limited to compiling your code, run the program, reason in your head how it works and use the debugger as a poor man’s REPL.

But wait…​ n is for network. Can I use this on other machines? Yes…​ yes you can.

shadow-cljs and webapps

The following script is called with remote-shadow-cljs.sh <ip-or-url>. It’s setup to allow multiple developers to develop against a frontend based on shadow-cljs, where the frontend is not running on the local computer, but instead on a phone or a tablet.

dev-index-browser.html is an HTML file which you use a template for injecting the URL/IP that shadow-cljs is running from. The only thing you now need is ${url} in the HTML file, where it should point towards the URL/IP where shadow-cljs is running.

It’s also very possible and easy to add extra URLs/IPs if need be.

remote-shadow-cljs.sh
#!/bin/bash

APP_URL=$1

URL_RX='\${url}'

prep_index () {
        echo "Preparing index.html for remote REPL"
        rm www/index.html
        cp dev-resources/dev-index-browser.html www/index.html
        sed -i -e "s/$URL_RX/http:\/\/$APP_URL\//g" www/index.html
}

prep_index


echo "Running: shadow-cljs watch :web --config-merge '{:asset-path \"http://$APP_URL:8700/js/compiled\", :devtools {:devtools-url \"ws://$APP_URL:9630/wsapp\"}}'"
shadow-cljs watch :web --config-merge "{:asset-path \"http://$APP_URL:8700/js/compiled\", :devtools {:devtools-url \"ws://$APP_URL:9630/wsapp\"}}"

If you supplement it with SSH tunneling it’s also possible to develop against a public domain name. You would need to adapt the script with regards to ports and protocol (HTTP or HTTPS).

shadow-cljs and cordova

If you want to wrap your webapp in a container, Cordova is a good choice. You now definately have a need for nREPL to work with a remote connection to Cordova.

The script (shoutout to Daniel Wasa Delatre for the MacOS vs Linux check):

cordova-shadow-cljs.sh
#!/bin/bash

RX='([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])'

case "$(uname -s)" in
    Darwin)
        DEV_IP=$(ip route get 1 | head -1 | cut -d' ' -f8)
        ;;
    Linux)
        DEV_IP=$(ip route get 1 | head -1 | cut -d' ' -f7)
        ;;
esac

echo "Running: shadow-cljs watch :cordova --config-merge '{:asset-path \"http://$DEV_IP:8700/js/compiled\", :devtools {:devtools-url \"ws://$DEV_IP:9630\"}}'"
shadow-cljs watch :cordova --config-merge "{:asset-path \"http://$DEV_IP:8700/js/compiled\", :devtools {:devtools-url \"ws://$DEV_IP:9630\"}}"
Note
The start HTML file (typically index.html) is specified in the config.xml that Cordova uses.

Regular clojure and nREPL

Working with nREPL on a different machine is a fairly straightforward process. Start the nREPL on that machine and connect to it via the IP and port.

There are two things you can do to make your life a lot easier if you do this on a a regular basis.

The first thing you can do is to set the port in the configuration to always be the same. In leiningen, which is what I’ve used the most, you would set the port for the machine in ~/.lein/profiles.clj with {:repl-options {:port <port-number}}. With this simple configuration you achieve ease of use (always same port) and keeping the configuration out of the project.

The second thing you can do is to make the IP/URL to the machine always be the same.

  • Control over the local network can afford to give you name based addresses to the machine, so that you don’t need to know the IP address.

  • You can use a VPN to make sure you always have the same IP to the machine, regardless of what network the machine is on. I have used Wireguard to great effect here.

Or use Calva

PEZ (Peter Strömberg) seems to be in hyperdrive to make life easier for us poor programmers. I’m still an Emacs fanboy, but whenever I try out Calva I’m impressed by how easy the UI is to hook things up is. Instead of fighting with the tooling, you just get started with cracking away at the problem you want to solve.

That said, some of the things mentioned are probably still of use.

Happy coding :).

Tags: clojure