Introduction

shadow-cljs is a clojurescript development environment that integrates seamlessly with the npm ecosystem to manage JavaScript dependencies. There are also many other conveniences that the author (and contributors - one of which inspired me to write this up) of shadow has provided the JavaScript-come-Clojurescript developer. Herein, we're going to run through how to get setup with shadow. This guide will focus on Atom, but there are many other IDE/text editors you can use for this.

Caveat:

This is going to be a whirlwind tour of shadow-cljs for JavaScript developers who use Atom. It was created in a bit of a hurry, so if you find anything confusing please highlight were you’re getting lost and I’ll follow up. If you’ll provide suggestions for improvement - they’ll be incorporated.

Prerequisites

ProtoREPL in Atom

The author of ProtoREPL has put together a marvelous setup guide, which you can find here. While the author uses Leiningen, you can skip those steps if you’d like. However, I find that there’s very little overhead and — perhaps more importantly — just following all the steps is probably the fastest/clearest path. As a bonus, you’ll already be setup for Clojure AND Clojurescript development by just following the steps there and here.

Java SDK:

Both Leiningen and Shadow require a modern version of Java SDK installed. The recommended version — as of the time of writing — is version 8, which you can find here. Don’t worry, you won’t have to touch Java for Clojurescript. It just needs the compiler.

Install the Shadow CLI

npm install -g shadow-cljs

Shadow-cljs Setup

Follow along with a working project:

https://github.com/loganpowell/shadow-proto-starter

Local Files

In the root directory of your project, create a shadow-cljs.edn file. At the top of shadow-cljs.edn add line:

{:source-paths ["src"]}

You can also generate a starter shadow-cljs.edn file from your terminal with shadow-cljs init .

Create a src folder in the root directory of your project. The src folder is where you'll create a user.clj file, wherein you include the short program:

(ns user)

(defn reset [])

As mentioned in the shadow docs: “The file must define the user/reset fn since Proto REPL will call that when connecting. If user/reset is not found it will call tools.namespace which destroys the running shadow-cljs server. We don’t want that.”

No, we don’t want that.

Edit the shadow-cljs.edn file by adding [proto-repl "0.3.1"] to the :dependencies key and set the :nrepl port (using 3333 here, but you can use whatever you prefer). Find out more about dependencies in the docs. It should look like this:

{:source-paths ["src"]

:dependencies [[proto-repl "0.3.1"]]

:nrepl {:port 3333}}

Add the following scripts to your package.json file and install the necessary dependencies:

"scripts": {

"dev": "shadow-cljs watch app",

"release": "shadow-cljs release app"

},

"devDependencies": {

"shadow-cljs": "<version>"

}

Once that’s set up, in your terminal, run: shadow-cljs server

You should now have the most basic shadow server up and running. In your terminal you should see:

...

shadow-cljs - starting ...

shadow-cljs - Using IP "10.0.75.1" from Interface "Hyper-V Virtual Ethernet Adapter #2"

shadow-cljs - server version: 2.3.23

shadow-cljs - server running at http://0.0.0.0:9630

shadow-cljs - socket repl running at localhost:52249

shadow-cljs - nREPL server started on port 3333

upon running the first time, you will have a new folder in your project: .shadow-cljs

Atom Settings

Now that you have your base project setup, you can get the fun part: Using the REPL!!

In Atom use your Atom Command Pallet (mine is ctrl+shift+p ) and find:

Proto Repl: Remote Nrepl Connection

In the dialog that pops up leave the setting for “host”: localhost enter the :nrepl port you set in your shadow-cljs.edn file:

3333 in this case

Kicking the Tires

Now, just try out some Clojure(script) in the REPL that pops up. Something like:

(+ 1 2)

Hit shift+enter and you should see 3 in the REPL. If you got that, congrats! We're now ready for development!

Project Deployment Targets

There are a number of :target s you can set Shadow for.

Each build in shadow-cljs must define a :target which defines where you intend your code to be executed. There are default built-ins for the browser and node.js. They all share the basic concept of having :dev and :release modes. :dev mode provides all the usual development goodies like fast compilation, live code reloading and a REPL. :release mode will produce optimized output intended for production.

Targets are covered in separate chapters.

Here are some of them:

:browser Output code suitable for running in a web browser.

:bootstrap Output code suitable for running in bootstrapped cljs environment.

:browser-test Scan for tests to determine required files, and output tests suitable for running in the browser.

:karma Scan for tests to determine required files, and output karma-runner compatible tests. See Karma.

:node-library Output code suitable for use as a node library.

:node-script Output code suitable for use as a node script.

:npm-module Output code suitable for use as an NPM module.

This means that you can — with Shadow — deploy an app, a node module or a simple node script without ever leaving Shadow. Also, perhaps in the not too distant future, you may see React Native on the list!

We are going to cover two of these:

Web Application Node Library

1) Setting up the Project for Building a Web Application

Let’s setup a project for building a web app! If you’d like to see more configuration notes, please check out this entry in the shadow github.

Augmenting shadow-cljs.edn

If you’ll recall in our package.json file:

"scripts": {

"dev": "shadow-cljs watch app",

"release": "shadow-cljs release app"

}

We have app set in our scripts. This is the name of the build that we'll make our web app with. We can change this to anything we wish, but we'll stick with it for now.

We are going to add some config to our shadow-cljs.edn file:

{:source-paths ["src"]

:dependencies [[proto-repl "0.3.1"]]

:nrepl {:port 3333}

;; NEW STUFF BELOW ;;

:builds

{:app {:target :browser

:output-dir "public/js"

:asset-path "js"

:modules {:main {:entries [app.core]}

:devtools {:before-load app.core/stop

:after-load app.core/start

:http-root "public"

:http-port 8020}}}

Let’s break this down one line at a time:

; This key tells shadow what builds we have for our app

:builds



; This is the name we'll give our first build

{:app ; We're targeting the browser

{:target :browser ; In our ["src"] where all our build files will go

:output-dir "public/js" ; The destination - relative to the :output-dir - of the compiled Clojurescript

:asset-path "js" ; The :modules section of the config is always a map keyed by module ID.

:modules ; The module ID is also used to generate the Javascript filename. Module :main will generate main.js in :output-dir.

{:main ; The entry file's namespace. See Note 1) below

{:entries [app.core]}} ; The :devtools section see Reference 2) in the References at the Bottom of this Section

:devtools ; A symbol (with namespace) of a function to run just before refreshing files that have been recompiled.

{:before-load app.core/stop ; A symbol (with namespace) of a function to run after hot code reload is complete.

:after-load app.core/start ; The disk path from which to serve root filesystem requests. If not supplied, no disk files are served.

:http-root "public" ; The port to serve from. See more in References

:http-port 8020}}}}

Notes:

:build setup is structured like this:

:builds

{:app {:target :browser

:output-dir "public/js"

:asset-path "/js"

:modules {:main {:entries [app.core]}}}}}

whose file structure should look like this:

.

├── package.json

├── shadow-cljs.edn

└── src

└── app

└── core.cljs

Notice that the folder structure must mimic the :entries namespace. The convention being that the highest level namespace ( app ) is a folder right below the src path and first child namespaces ( core ) is directly under the highest level namespace ( app.core = app/core ).

You can find out more about these settings in the documentation

Create an index.html file inside your public directory

Create an html file that looks something like this:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Starter project</title>

</head>

<body>

<div id="app"></div> <!-- your build -->

<script src="/js/main.js"></script> <!-- :output-dir -->

<script>app.core.init();</script><!-- "Starter file" -->

</body>

</html>

Create a Starter File

In your core.cljs file (if you're following along), create a simple starter file like this:

(ns app.core)



(js/console.log "Hey from proto-repl!")



(js/alert "foo") ; This is the :devtools {:before-load script

(defn stop []

(js/console.log "Stopping...")) ; This is the :devtools {:after-load script

(defn start []

(js/console.log "Starting...")) ; This is the `app.core.init()` that's triggered in the html

(defn ^:export init []

(start))

Fire it up!

Now we should be able to restart our shadow in our terminal:

npm run dev

For Node development — unlike when targeting the browser — you’ll use:

(shadow.cljs.devtools.api/node-repl)

to wake up ProtoREPL to your build. I made the mistake of not eval’ing this and got stuck for a while trying to figure out what I did wrong. Don’t be like me. Eval!

Execute the block of code using ProtoREPL ( ctrl+, b / ctrl+alt, b ):

(prn "Hey from proto-repl!")