ClojureScript

Quick Start

If you are using macOS or Linux the only dependencies required for the main part of this tutorial are a web browser and an installation of Clojure. On Windows you will need Java 8 and the standalone ClojureScript JAR. Note that the requirement of a web browser excludes headless environments, and we then recommend skimming to the Node.js portion of the tutorial.

Hello, ClojureScript

In this tutorial we will guide you through compiling and running a simple ClojureScript project, as well as running REPLs to interactively develop and test your code. The tutorial only assumes basic familiarity with the command line.

First set up a project folder for our Hello World program. Here’s a list of the files and folders you’ll need. Note that the directory structure and the underscores in the names are important and should not be changed.

hello-world        # Our project folder
├─ src             # The CLJS source code for our project
│  └─ hello_world  # Our hello_world namespace folder
│     └─ core.cljs # Our main file
├─ cljs.jar        # (Windows only) The standalone Jar you downloaded earlier
└─ deps.edn        # (Mac/Linux only) A file for listing our dependencies

If you are on OS X or Linux your deps.edn file should contain the following:

{:deps {org.clojure/clojurescript {:mvn/version "1.10.329"}}}

In your favorite text editor edit the src/hello_world/core.cljs to look like the following:

(ns hello-world.core)

(println "Hello world!")

Now that we have a simple program, let’s build and run some ClojureScript:

clj --main cljs.main --compile hello-world.core --repl

On Windows:

java -cp "cljs.jar;src" cljs.main --compile hello-world.core --repl

Your default web browser will open to a page that looks like the following:

Browser REPL Serving Default index.html

After a couple of seconds you will see Hello world! print at the terminal and you will be given a REPL prompt. Try evaluating some expressions like the following:

(inc 1)
(map inc [1 2 3])
(.getElementById js/document "app")

Let’s look a bit closer at the flags we’ve used here. --main invokes a Clojure function, in this case cljs.main. The cljs.main function supports a variety of command line arguments to specify common tasks. We’re using --compile to specify that we want to compile the hello-world.core namespace. This is followed by --repl to say that we want a REPL to launch immediately when compilation completes.

Change your src/hello_world/core.cljs source file to look like the following:

(ns hello-world.core)

(println "Hello world!")

;; ADDED
(defn average [a b]
  (/ (+ a b) 2.0))

At the REPL prompt, recompile and reload your namespace by evaluating the following:

(require '[hello-world.core :as hello] :reload)
(hello/average 20 13)

You should see the result 16.5.

Let’s make a mistake. Try evaluating (ffirst [1]). You should get a source mapped stack trace pointing at ClojureScript source locations not JavaScript ones. This makes debugging a lot nicer.

You can easily explore the options provided by the ClojureScript compiler by using --help. You will see there are abbreviated flags for all the options we used thus far, -m for --main, etc.

clj -m cljs.main --help

On Windows:

java -cp "cljs.jar;src" cljs.main --help

One important thing to note is that so called main options like --compile, --main, --repl must always come last.

Production Builds

You may have noticed the out directory which contains all of the compiled JavaScript. There’s about 6 1/2 megabytes worth of JavaScript in there. This may seem unwieldy but fortunately the ClojureScript compiler generates output optimized for the Google Closure Compiler. The Google Closure Compiler performs many optimizations and the most significant for browser-based clients are minification and dead code elimination.

Let’s remove the REPL modifications we made earlier from src/hello_world/core.cljs:

(ns hello-world.core)

(println "Hello world!")

We can create a release build by setting the appropriate value for the --optimizations flag. The default optimization level is none, but this time we want to use all the optimizations provided by both ClojureScript and Google Closure Compiler - this can be done by specifying advanced. Other valid options for --optimizations are whitespace and simple but these are less commonly used:

clj -m cljs.main --optimizations advanced -c hello-world.core

On Windows:

java -cp "cljs.jar;src" cljs.main --optimizations advanced -c hello-world.core

This process will take significantly longer which is why we don’t use this compilation mode for development.

Examine out/main.js, the file size should be around 90K. If you zip this file you’ll see that it’s around 20K. This is significantly smaller than a jQuery dependency yet when using ClojureScript you have implicit dependencies on the entire ClojureScript standard library (10KLOC) and the Google Closure Library (300KLOC). You can thank dead code elimination.

You can test that this file still works by running the built in simple web server via the --serve flag:

clj -m cljs.main --serve

On Windows:

java -cp "cljs.jar;src" cljs.main --serve

This command does not start a REPL, so a browser window will not be automatically opened. Navigate to http://localhost:9000 using your favorite browser. Check the JavaScript Console, you should see Hello world! printed. The builtin web server gzips JavaScript content. Check your browser’s JavaScript Console Network tab and you should be able to confirm that the total JavaScript payload is now around 20K.

Running ClojureScript on Node.js

First make sure you have Node.js installed. For instructions on installing Node.js, see the Node.js wiki. Only the current stable versions of Node.js (>= 0.12.X) are supported at this time.

Before we proceed, enable source mapping:

npm install source-map-support

Let’s build your Node project. We can specify that we want to generate code for a specific JavaScript target with --target. If no --target flag is supplied, ClojureScript generates code for browsers. Other valid but less common options are nashorn and rhino. We’re also using --output-to here for specifying the --output-to file:

clj -m cljs.main --target node --output-to main.js -c hello-world.core

On Windows:

java -cp "cljs.jar;src" cljs.main --target node --output-to main.js -c hello-world.core

You can run your file with:

node main.js

Note: Under Node.js there is little reason to use advanced optimizations. While advanced optimizations does apply performance related optimizations, these are now largely obviated by optimizations present in modern JavaScript virtual machines like V8, SpiderMonkey, and JavaScriptCore. For Node.js, simple or none optimizations suffice.

Node.js REPL

Running a Node.js REPL is similar to running a browser REPL. In order to specify a REPL which uses a different JavaScript evaluation environment you supply --repl-env. This value defaults to the browser REPL but in this case we want to specify node.

clj -m cljs.main --repl-env node

On Windows:

java -cp "cljs.jar;src" cljs.main --repl-env node

All the previously described REPL interactions for the browser should work.

Dependencies

ClojureScript supports a wide variety of options for including ClojureScript and JavaScript dependencies (see Dependencies for details).

React is a popular dependency for ClojureScript projects. CLJSJS provides a bundled version. Let’s see how to include it.

Modify your deps.edn file:

{:deps {org.clojure/clojurescript {:mvn/version "1.10.329"}
        cljsjs/react-dom {:mvn/version "16.2.0-3"}}}

Let’s edit our simple program to look like the following so that React is properly required:

(ns hello-world.core
  (:require react-dom))

(.render js/ReactDOM
  (.createElement js/React "h2" nil "Hello, React!")
  (.getElementById js/document "app"))

Let’s build and run:

clj -m cljs.main -c hello-world.core -r

When the browser launches you should momentarily see the default page which will then be quickly replaced by a h2 tag containing Hello React!.

Original author: David Nolen