Skip to content
Mike Fikes edited this page Mar 9, 2015 · 218 revisions

The only dependencies required for this tutorial is an installation of Java 8 and the standalone ClojureScript JAR. ClojureScript itself only requires Java 7 but the standalone JAR comes bundled with useful Nashorn integration that requires Java 8.

You must have Java 8 installed to proceed.

Even if you are interested in a Leiningen based workflow this Quick Start is essential reading. It covers the fundamentals regardless of what tooling you decide to end up using.

ClojureScript Compiler

The standalone ClojureScript JAR comes bundled with Clojure 1.6.0. This supports simple scripting of the ClojureScript compiler and the bundled REPLs without an overly complicated command line interface.

Download the standalone ClojureScript JAR.

Create a directory hello_world and copy the JAR into that directory, then from inside the hello_world directory:

mkdir -p src/hello_world;touch src/hello_world/core.cljs

For Windows, you'll need to install a touch utility and enter:

mkdir src\hello_world & touch src\hello_world\core.cljs

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

(ns hello-world.core)

(enable-console-print!)

(println "Hello world!")

First we declare our namespace. Every ClojureScript file must declare a namespace and this namespace must match a path on disk. We then direct printing to the commonly available JavaScript console object and print the famous message.

In order to compile this we need a simple build script. ClojureScript is just a Clojure library and can be easily scripted in a few lines of Clojure. Create a file called build.clj in the current directory not in src. Note the name of this file does not matter. In this tutorial we'll always make our compiler helper scripts at the root of the project directory.

Add the following Clojure code:

(require 'cljs.closure)

(cljs.closure/build "src" {:output-to "out/main.js"})

We require the cljs.closure namespace. We then invoke the standard function for building some ClojureScript source - cljs.closure/build. This function only takes two arguments: the directory to compile and a map of options. In our case a simple :output-to will suffice for now.

Let's build some ClojureScript:

java -cp cljs.jar:src clojure.main build.clj

We invoke java and set the classpath to our JAR and the directory where our ClojureScript code lives. The clojure.main argument in this case allows us to easily execute a Clojure file.

Control should return to the shell relatively quickly and you will have an out directory with compiled JavaScript including your simple program. You will see that many files were produced in addition to the out/main.js we specified. We'll explain this momentarily but first let's see how you can easily include the compiled output on a web page.

Using ClojureScript on a Web Page

Create a file index.html and include the following:

<html>
    <body>
        <script type="text/javascript" src="out/main.js"></script>
    </body>
</html>

Open this file in your favorite browser and find the JavaScript developer console so you can see the output.

You will not see "Hello world!" instead you will likely see an error like the following:

Uncaught ReferenceError: goog is not defined

In order to understand this error we must examine a few basics around the Google Closure Library. While the following section may seem somewhat roundabout, a short review on how Google Closure Library works will make simple bugs considerably easier to spot.

Google Closure Library

In order to abstract away JavaScript environment differences ClojureScript relies on the Google Closure Library (GCL). GCL supplies an important facility missing from JavaScript: namespaces and a way to declare dependencies between them. In fact ClojureScript namespaces get compiled to Google Closure namespaces.

Loading dependencies correctly across various browser targets is a surprisingly tricky affair. GCL accomplishes this by maintaining a dependency graph. When you require a namespace it will write the needed script tags in dependency order for you.

So what went wrong? If you look at out/main.js you will see some dependency graph building calls:

goog.addDependency("base.js", ['goog'], []);
goog.addDependency("../cljs/core.js", ['cljs.core'], ...);
goog.addDependency("../hello_world/core.js", ['hello_world.core'], ...);

But wait, where is this goog object coming from?

Oops. We never loaded it! In order for GCL to bootstrap we must at least load goog/base.js. You'll see this is available in out/goog/base.js. Let's add this to your page now:

<html>
    <body>
        <script type="text/javascript" src="out/goog/base.js"></script>
        <script type="text/javascript" src="out/main.js"></script>
    </body>
</html>

Refresh the page.

The error will be gone but you still won't see the desired "Hello world!".

Hrm. out/main.js didn't appear to have any of the logic that we wrote, in fact it only includes the needed dependency graph information for the ClojureScript standard library cljs.core and our namespace.

Ah. The last step we missed was actually requiring our namespace to kick things off. Change index.html to the following.

<html>
    <body>
        <script type="text/javascript" src="out/goog/base.js"></script>
        <script type="text/javascript" src="out/main.js"></script>
        <script type="text/javascript">
            goog.require("hello_world.core");
        </script>
    </body>
</html>

Refresh your index.html and you should finally see "Hello world!" printing to the browser JavaScript console. If you're using a sufficiently modern browser you should even see the printing was invoked from a ClojureScript source file and not a JavaScript one thanks to source mapping (some browsers like Chrome require you to first enable source mapping, for more details look here.

Less Boilerplate

The previous section explained some important fudamental concepts around the Google Closure Library. However it also involved a substantial amount of boilerplate. One way to eliminate this is specify a :main entry point in the options that you pass to cljs.closure/build. Let's do that now:

(require 'cljs.closure)

(cljs.closure/build "src"
  {:main 'hello-world.core
   :output-to "out/main.js"})

Change your HTML to the following:

<html>
    <body>
        <script type="text/javascript" src="out/main.js"></script>
    </body>
</html>

Rebuild:

java -cp cljs.jar:src clojure.main build.clj

Refresh the page and you should still see "Hello world!" printed to the JavaScript console. If you examine out/main.js you'll see that it writes out the boilerplate script tags for you.

Auto building

The ClojureScript compiler supports incremental compilation. It's convenient to have the ClojureScript compiler watch a directory and recompile as needed. Let's make a new helper script watch.clj:

(require 'cljs.closure)

(cljs.closure/watch "src"
  {:main 'hello-world.core
   :output-to "out/main.js"})

Let's start auto building:

java -cp cljs.jar:src clojure.main watch.clj

You should see output like the following:

Building ...
Reading analysis cache for jar:file:/.../cljs.jar!/cljs/core.cljs
Analyzing src/hello_world/core.cljs
... done. Elapsed 1.425505401 seconds

Edit src/hello_world/core.cljs. You should see recompilation output.

Browser REPL

It's hard to imagine a productive Lisp experience without a REPL (Read-Eval-Print-Loop). ClojureScript ships with builtin REPL support for Node.js, Rhino, Nashorn, and browsers.

Let's hook up a browser REPL to our project.

First it is recommended that you install rlwrap. Under OS X the easiest way is to use brew and brew install rlwrap.

Let's create a REPL script repl.clj:

(require 'cljs.repl)
(require 'cljs.closure)
(require 'cljs.repl.browser)

(cljs.repl/repl (cljs.repl.browser/repl-env)
  :watch "src"
  :init (fn []
          (cljs.closure/build "src"
            {:main 'hello-world.core
             :output-to "out/main.js"})))

REPLs are always constructed in the same way. The first argument to cljs.repl/repl is the REPL evaluation environment (Node.js, Rhino, Nashorn, browser), the subsequent arguments are the same arguments you pass to cljs.closure/build in addition to several options that are specific to REPLs. Note that we supply a :watch option with a source directory. This conveniently starts a REPL along with an auto building process. The auto building process will write its activity to out/watch.log so you can easily tail -f out/watch.log. We also supply the :init option to build the main namespace at least once.

We also need to modify our script to load the browser REPL:

(ns hello-world.core
  (:require [clojure.browser.repl :as repl]))

(defonce conn
  (repl/connect "http://localhost:9000/repl")) 

(enable-console-print!)

(println "Hello world!")

We create the connection with defonce. This ensures the connection is constructed only one time - we may reload this namespace during development and we don't want multiple connection instances.

Let's try it:

rlwrap java -cp cljs.jar:src clojure.main repl.clj

The first time will be somewhat slow as the REPL communication script needs to built. You will also see innocuous WARNINGs from the Google Closure Compiler that can be ignored. You should eventually see the following message:

Waiting for browser to connect ...

Point your web browser at http://localhost:9000.

You should get a REPL. If it doesn't connect immediately try refreshing the browser a few times (Chrome & Firefox tend to more stable than Safari). Try evaluating a simple expression like (+ 1 2).

Run tail -f out/watch.log in a fresh terminal to view auto build progress.

Try evaluating some expressions like (first [1 2 3]), or (doc first), (source first).

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

(ns hello-world.core
  (:require [clojure.browser.repl :as repl]))

(defonce conn
  (repl/connect "http://localhost:9000/repl"))

(enable-console-print!)

(println "Hello world!")

(defn foo [a b]
  (+ a b))

Require your namespace by evaluating (require '[hello-world.core :as hello]). Try evaluating (hello/foo 2 3), you should get the result 5.

Change your source file so that foo uses * instead of +:

(ns hello-world.core
  (:require [clojure.browser.repl :as repl]))

(defonce conn
  (repl/connect "http://localhost:9000/repl"))

(enable-console-print!)

(println "Hello world!")

(defn foo [a b]
  (* a b)) ;; CHANGED

We can get this definition by doing a require that forces a reload. Evaluate (require '[hello-world.core :as hello] :reload) and try (hello/foo 2 3) you should get 6 this time.

Lets 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.

Production Builds

You may have noticed that out contains a lot of JavaScript. Fortunately the ClojureScript compiler generates output optimized for the Google Closure Compiler. The Google Closure Compiler performs many optimization, but the most significant for browser based clients are minification and dead code elimination.

Let's make a new helper build script release.clj, it should look like the following:

(require 'cljs.closure)

(cljs.closure/build "src"
  {:output-to "out/main.js"
   :optimizations :advanced})

Under :advanced optimizations :main is not needed as advanced compilation creates a single JavaScript artifact.

Let's remove the dev time REPL bits from src/hello_world/core.cljs:

(ns hello-world.core)

(enable-console-print!)

(println "Hello world!")

Let's create a release build:

java -cp cljs.jar:src clojure.main release.clj

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

Open index.html, you should still see "Hello world!" printed.

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

Running ClojureScript on Node.js

To run ClojureScript on Node.js, set the var *main-cli-fn* to the function you want to use as an entrypoint. 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. The example below shows how a functional programmer might print "Hello World".

(ns hello-world.core
  (:require [cljs.nodejs :as nodejs]))

(nodejs/enable-util-print!)

(defn -main [& args]
  (println "Hello world!"))

(set! *main-cli-fn* -main)

Make a build helper file called node.clj:

(require 'cljs.closure)

(cljs.closure/build "src"
  {:main 'hello-world.core
   :output-to "main.js"
   :target :nodejs})

The only differences are that we had to specify a :nodejs target and we do not output main.js to the out directory. This is important due to the way that Node.js resolves JavaScript source files.

Node.js has great source mapping support, in order to enable it just install source-map-support:

npm install source-map-support

Let's build your Node projects:

java -cp cljs.jar:src clojure.main node.clj

You can run your file with:

node main.js

Node.js REPL

Running a Node.js REPL is much simpler than running a browser REPL. Create a helper build file called node_repl.clj that looks like the following:

(require 'cljs.repl)
(require 'cljs.closure)
(require 'cljs.repl.node)

(cljs.repl/repl (cljs.repl.node/repl-env)
  :watch "src"
  :init (fn []
          (cljs.closure/build "src"
            {:main 'hello-world.core
             :output-to "out/main.js"})))

There's no need to add any REPL specific bits to src/hello_world/core.cljs, make sure it looks as described in the previous section.

Let's start the REPL:

rlwrap java -cp cljs.jar:src clojure.main node_repl.clj

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

Clone this wiki locally