ClojureScript IntelliJ IDEA and shadow-cljs

August 24, 2018
Andrea Richiardi

We are growing the ClojureScript team here at ElasticPath and one of the first thing to get folks going is to setup their IDE so that they can start hacking.

I personally use Emacs, which I maintain is straightforward to configure thanks to the Cider plugin, but today we are going to configure IntelliJ IDEA: we will create a simple node script project that uses the shadow-cljs ClojureScript compiler plus Cursive IDE, an IntelliJ plugin.

Install IntelliJ IDEA

We will setup the Community Edition and we need the Cursive Plugin EAP, which you can find under the Plugins -> Browse Repositories and filter for Cursive. At the moment of this writing the version is v1.8.0-eap6.

We are using the EAP version because of its Clojure Deps experimental support. Please make sure to report issues to the Cursive project on GitHub if you find any.

The other thing you might want to do if your colleagues are using Emacs is to enable double semicolons for line comments. You can do that by checking the box Editor -> General -> Smart Keys -> Clojure -> Use ;; for line comments.

Install the Clojure Deps CLI

Check the official Getting Started, this is an easy one on MacOS:

brew install clojure

Create a Clojure Deps project

Select Create New Project -> Clojure -> Deps and name the project cursive-shadow-cljs.

Now you can replace the file with the following deps.edn. For more detail you can follow along with the Clojure Deps and CLI Guide.

{:paths ["src"]
 :aliases
 {:dev
  {:extra-deps  {org.clojure/clojure {:mvn/version "1.10.0-alpha6"}
                 org.clojure/clojurescript {:mvn/version "1.10.339"}}}
  :test
  {:extra-paths ["test"]}}}

The project is not going to contain the :deps key because we are not producing a library and therefore we are not going to need to declare transitive dependencies. It might look confusing but org.clojure/clojurescript is not a dependency of our project but only used at "dev" time for transpiling to JavaScript.

If you receive a Could not find clojure executable error in Cursive, it means that you have not installed the tooling correctly. Make sure the program clojure is on the PATH and is executable.

If you do not have the latest EAP or importing/creating using the Clojure Deps IntelliJ option is not working for you, you can copy the following dummy project.clj file to the project root:

(defproject cursive-shadow-cljs "0.0.0"
  :source-paths ["src"]
  :test-paths []
  :resource-paths []
  :compile-path nil
  :target-path nil
  :plugins [[lein-tools-deps "0.4.1"]]
  :middleware [lein-tools-deps.plugin/resolve-dependencies-with-deps-edn]
  :lein-tools-deps/config {:config-files [:install :user :project]
                           :aliases [:dev :test]})

Create a shadow-cljs project

Shadow integrates seamlessly with Clojure Deps files so, give we are targeting node, we only need to create a shadow-cljs.edn that looks like:

{:deps {:aliases [:dev :test]}
 :builds {:script {:target    :node-script
                   :main      cursive-shadow-cljs.core/-main
                   :output-to "lib/script.js"}}}

The :aliases key is pulling in the paths and the dependencies we specified above. The other thing worth noting is the :output-to path - keep that in mind, it will be necessary later.

We will also need a src/cursive_shadow_cljs/core.cljs file, creating the directories if necessary:

(ns cursive-shadow-cljs.core)

(defn -main [& cli-args]
  (print "hello world"))

Eagle-eyed hackers will notice that the package name corresponds to the dir name in src apart from the underscore/dash difference. Folders with underscores will map to packages with dashes.

Can you use directories with dashes? No it won't work ¯_(ツ)_/¯

Create a package.json

The package.json file will contain not only the JS dependencies you want to use but also aliases to common scripts: I found that yarn script-name or npx script-name are very useful shortcuts that the whole team can benefit from.

The nice thing is that shadow-cljs can be added to package.json so that you can ease the transition for developers that are used to the JavaScript workflow. We use yarn here so the following will be using that:

yarn init  # will generate the package.json
yarn add shadow-cljs --dev

You can also just paste this directly

{
  "name": "cursive-shadow-cljs.",
  "version": "0.0.0",
  "scripts": {
    "build:watch": "shadow-cljs watch script",
    "build": "shadow-cljs release script"
  },
  "devDependencies": {
    "shadow-cljs": "2.6.2",
  },
  "dependencies": {}
}

Side note: the create-cljs-project project template, new addition by Thomas Heller - shadow-cljs's maintainer, is available on NPM.

In order to create the project you only need

yarn create cljs-project cursive-shadow-cljs

Useful after having digested this post in order to speed up a couple of steps.

Launch shadow-cljs

Shadow is a very flexible yet minimal tool. The quickest way to start to a REPL is by using the watch command:

  • In a terminal, run
yarn shadow-cljs watch script

Or

yarn build:watch

This terminal can be then buried and forgotten, it does not need to be restarted ever during your workflow. The only exception being when you need to add/remove dependencies.

  • Run -> Run... -> Edit Configurations... -> Clojure REPL -> Remote

Connect to Lein REPL port

The Shadow CLJ Remote configuration is now ready to be executed - you will see some logging of Cursive connecting to the REPL.

  • Evaluate something in the window below the logging you see

Clojure REPL

You are now using the Clojure REPL.

REPL driven build

Shadow really embodies the REPL-driven development approach. Try this:

(shadow/)  ;; then CTRL-SPACE

You will see a bunch of functions you can call. Most of them are the same you can run from the command line (like shadow/watch). By convention the ones ending with a star are alternative implementations to the ones without.

The one you need for starting a ClojureScript REPL is shadow/repl:

(shadow/repl :script)

ClojureScript REPL

The dropdown should change and the namespace you are in is now cljs.user. Try to execute another time that println command:

(println "Hi from the REPL")

No application has connected to the REPL server.
Make sure your JS environment has loaded your compiled ClojureScript code.

Basically the error is telling us that it cannot run anything without a JavaScript runtime. It makes sense, at the end of the day shadow-cljs is a Clojure program running on the JVM that compiles to JavaScript.

Remember the :output-to location?

We need to run that JavaScript file in the node runtime in order to get to our REPL, therefore we run in a terminal:

node lib/script.js
hello world

You will notice a JS runtime connected. message in the REPL. You've finally reached the ClojureScript node REPL!

Bonus - Cursive keybinding

The goal of this post is to how to setup Cursive and so far it has been a smooth sail. However, there is one more thing that can improve your workflow.

By right clicking on a Clojure file you can find a very useful REPL -> Add new REPL command entry.

Add New REPL Command

This allows you to create command strings to send to the REPL so that you can bind them to keyboard shortcuts.

Create REPL Command

This will bring you closer to the better Emacs workflow, except that you do not have the flexibility to go and tweak the IntelliJ dialog into asking the build id you want to work against - it has to be edited manually every time. Nonetheless, this concludes our guide.

Your REPL should be all set and you are good to go, you can find a working project containing the above code on GitHub (make sure you explore the branches):

Other Links