ClojureScript dev env in a container w/ VS Code
Tags: clojure, vscode, docker
Published: 2020-08-01 10:05:22

_Documenting how I setup ClojureScript dev environment in a container for my future self._

VS Code has a neat extension called Remote - Containers. It allows you to open a folder inside a docker container. If I put the puzzle pieces together, that means I can work on my local source code folder inside a docker container that has a dev environment already setup. You can configure it to run specific VS Code extensions while running inside the container... like ones that add language support.

# Install "Remote - Containers" extension

Open VS Code first, and install the Remote - Containers extension. (Gratuitous dashes why?) Then to have it pre-make some example files for you, open a folder first where you want to keep the files, then open the Command Palette (F1) and look for Remote - Containers: Try a sample. It doesn't really matter which sample you pick as we will be editing the generated files. It should create a `.devcontainer` folder with two files in it.

> VS Code may prompt you to reopen in a container after this. But I didn't just yet because I wanted to edit the configuration.

## `.devcontainer/Dockerfile`

I could not find a prebuilt ClojureScript docker image. But it is just the Clojure image with Node.js installed. So I did some googling and pieced that together.

_Edit: Added git to Dockerfile_

```dockerfile
FROM clojure:latest

RUN apt-get update
RUN apt-get -y install git-core
RUN apt-get -y install curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_12.x  | bash -
RUN apt-get -y install nodejs
RUN npm install
```

I'm not super thrilled with curling a script off the internet and running it. So if anyone has a better alternative, I would love to see it.

## `.devcontainer/devcontainer.json`

Here I changed the name, added the Calva extension for Clojure integration, added forwarded ports, and removed extra stuff specific to the sample. The port 8080 is for serving the compiled assets using a dev web server and 9630 is for the REPL.

```json
// For format details, see https://aka.ms/vscode-remote/devcontainer.json
{
	"name": "ClojureScript",
	"build": {
		"dockerfile": "Dockerfile",
		"context": ".."
	},

	// Set *default* container specific settings.json values on container create.
	"settings": { 
		"terminal.integrated.shell.linux": "/bin/bash"
	},

	// Add the IDs of extensions you want installed when the container is created.
	"extensions": ["betterthantomorrow.calva"],

	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	"forwardPorts": [8080, 9630]
}
```

# Running in container

> I should probably point out the implicit assumption that Docker has already been installed.

At this point you can start things up by opening the Command Palette (F1 in case you forgot) and running Remote - Containers: Reopen Folder in Container.

You might well run into errors that you have to work through. I only ran into one which I will document. Any others you get, you will have to type into DuckDuckGo (but then probably Google for better search results) to find answers yourself.

## Windows: file sharing canceled

_What isn't canceled these day, am i right??? Too soon?_

I immediately got an error, "file sharing canceled" or somesuch, because it had been a while since I ran Docker Desktop. They used to have this horrendous model where you had to share your entire hard drive. Well, they changed that but it means files were inaccessible to Docker instances now. So I had to open Docker Desktop settings, Resources, File Sharing, and add my code folder to the list.

While I was at it I turned off the offensively opt-out telemetry in the General tab.

# Initializing shadow-cljs

From here you can copy in or initialize whatever template you want. I chose to do a new shadow-cljs project.

I opened a "terminal" (a bash shell). Then at the advice of the shadow-cljs documentation I ran:

```bash
npx create-cljs-project my-project
```

Unfortunately, this command refuses to run on an already existing folder. It insists on creating a new folder. So then I moved all the stuff it generated up a level.

```bash
mv my-project/* .
rmdir my-project
```

# Open questions

I would appreciate suggestions on the stuff below.

## An MVU library

I've look through several UI libraries for ClojureScript, re-frame being the most obvious. But I haven't quite found what I am looking for. I would like an experience similar to F# Elmish. It is awesome because it really only brings an organization structure to your code -- admittedly with challenging constraints -- but not any major abstractions to learn.

> Abstraction is a good thing, but only when it gets out of the way. When you have to learn the abstraction's rules and caveats, it _adds_ complexity rather than reducing it.

My initial plan will be to just use Reagent by itself. Maybe work on my own basic MVU cycle for this hobby project. I have some experience there anyway.

## PWA trappings

I want to make an offline only PWA utility app. So far I didn't find an npm package to create all the service worker, manifest, etc. stuff for a ClojureScript app. Crafting these by hand sounds... _I'm in my happy place. I'm in my happy place. I'm in my happy place._

## Shadow's folder structure

I think probably this is just being brainwashed by webpack, but it feels deeply weird that the web server serves content out of the source-controlled static assets folder `/public`. Then the git-ignored compiled code goes into `/public/js`. I'm more used to the static assets being copied into the wholly git-ignored output folder, and the dev web server using that. I guess it is not a big deal, it just struck me oddly.

## Hashing CSS file names

So the previous point kinda makes sense because shadow doesn't try to be webpack. It is mainly for compiling ClojureScript, not for all the bundling needs. But now I am wondering how I am going to give my CSS files hashed names. That will be important for cache-busting new releases.

# Good stuff

I slapped on some hiccup for Hello World to make sure I was getting some results in the browser. (It works on my machine!) And that was my first ever Clojure app.