ClojureScript for absolute beginners

This post takes you through the process of writing a simple ClojureScript program and running it in a web browser. It’s in three parts: Writing, Compiling and Running the program. It assumes no prior experience with Clojure, ClojureScript, Java, or much else apart from a working knowledge of JavaScript and the command line. You’ll need to have the Clojure language installed (instructions here), because the ClojureScript compiler is implemented in Clojure.

Finally, you’ll need a clean working directory in which to keep the files you’ll create.

Writing the program

We’re not going to go into the syntax and semantics of ClojureScript here — we’re just demonstrating the end-to-end journey from ClojureScript source to your browser executing it — so the following code should do for a first program.

(ns beginner.core)
(js/document.write “I have been compiled to JavaScript!”)

When we compile and run this, we expect it to print “I have been compiled to JavaScript!” using the trusty document.write() function from JavaScript.

You’ll notice it declares a namespace using ns. This is obligatory for all ClojureScript files, and it matters for two reasons. Firstly, it’s necessary to put the file at a path which exactly reflects that namespace, i.e. beginner/core.cljs. Secondly, that path needs to be somewhere the ClojureScript compiler can find it. By default, the compiler looks in a folder called src/. So relative to the clean working directory we start with, the file needs to live at src/beginner/core.cljs.

At this point, your directory ought to look something like this:

.
└── src
    └── beginner
        └── core.cljs

Compiling the program

The ClojureScript compiler works like this: you tell it the namespace of the ClojureScript program you want to compile, it gathers the namespace’s files from their predicatable location on disk (see above), and it produces some more files which are that program translated into JavaScript.

Because the ClojureScript compiler is a Clojure library, we need to call it from Clojure, and to do that, we need to let Clojure know we want to take a dependency on the compiler.

When you run Clojure using the built-in clj tool, it loads any dependencies defined in a deps.edn file (more about that) in the current working directory. So all that’s required to declare a dependency on the ClojureScript compiler is to create that file and insert the following:

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

Now your working directory looks like this:

.
├── deps.edn
└── src
    └── beginner
        └── core.cljs

And it’s possible to invoke the compiler from the command line:

clj --main cljs.main --compile beginner.core

NB it looks like we’re passing two options to clj: --main and --compile. Actually, we are passing --main to clj and --compile to cljs.main 🙃. In practice, everything after cljs.main is an option for ClojureScript, not for clj.  You can find a list of the available options here.

If all is well, this will print nothing and exit, and you should find a new folder in your working directory called out/. This holds our program. Now: how to run it?

Running the program

We want a web browser to execute the JavaScript we’ve just compiled, and we can achieve that by including the compiled file in a web page. Create a file called index.html in your working directory and insert the following markup:

<html>
    <script src="out/main.js"></script>
</html>

Now your working directory looks like this:

.
├── deps.edn
├── index.html
└── out
    └── main.js
    └── *some other stuff*
└── src
    └── beginner
        └── core.cljs

Open this file in your web browser, and you should see the words “I have been compiled to JavaScript!”

You have just compiled some ClojureScript. You created a ClojureScript file, placed it in a predictable location, and gave its namespace to the ClojureScript compiler. The compiler found your file using the namespace and turned it into JavaScript. You included that JavaScript in a web page, and it executed.

Inside /out

We blindly included out/main.js to get the browser to pick it up. Let’s have a look inside the out/ folder.

out/ contains many more files than main.js. After compiling the very simple program from the previous post, my out/ folder contains no fewer than 35 files in 18 directories: a total of 4.7 megabytes of code. For comparison, the program itself in plain JavaScript would be 54 bytes.

To experience vertigo, have a look inside main.js:

var CLOSURE_UNCOMPILED_DEFINES = {};
var CLOSURE_NO_DEPS = true;
if(typeof goog == "undefined") document.write('<script src="out/goog/base.js"></script>');
document.write('<script src="out/goog/deps.js"></script>');
document.write('<script src="out/cljs_deps.js"></script>');
document.write('<script>if (typeof goog == "undefined") console.warn("ClojureScript could not load :main, did you forget to specify :asset-path?");</script>');
document.write('<script>goog.require("process.env");</script>');
document.write('<script>goog.require("beginner.core");</script>’);

This doesn’t even contain our program code — just some jargon and a series of calls to document.write to load yet more scripts. The program is here somewhere, because it runs when we include main.js, but it’s not immediately obvious where.

What makes this file complicated is that our original program has a single enormous implicit dependency: the ClojureScript language itself. The JavaScript implementation of that depends in turn on another library called Google Closure (GCL). Therefore our out/ folder includes the entirety of the GCL as well as the JavaScript translation of the ClojureScript standard library, and main.js loads the lot via those document.write calls which are including other scripts.

All of them are interesting in different ways. A good place to start poking around in /out is cljs_deps.js. Its job is to declare all our dependencies: the GCL, whichever bits of ClojureScript we need in the form of .js files in the out/cljs folder, and finally our program:

goog.addDependency("base.js", ['goog'], []);
goog.addDependency("../cljs/core.js", ['cljs.core'], ['goog.string', 'goog.Uri', 'goog.object', 'goog.math.Integer', 'goog.string.StringBuffer', 'goog.array', 'goog.math.Long']);
goog.addDependency("../process/env.js", ['process.env'], ['cljs.core']);
goog.addDependency("../beginner/core.js", ['beginner.core'], ['cljs.core']);

It now seems pretty clear that we can look inside out/beginner/core.cljs and find our ClojureScript code as JavaScript, and here it is:

// Compiled by ClojureScript 1.10.520 {}
goog.provide('beginner.core');
goog.require('cljs.core');
document.write("I have been compiled to JavaScript");

//# sourceMappingURL=core.js.map

That’s the journey of a .cljs file into a .js file. Useful? Interesting? Wrong? Please leave a comment!

Join the Conversation

4 Comments

  1. That’s a really great intro.
    JS will need to keep an open mind though about all these dependencies.
    It’s a price to be paid for gains in productivity that will pay off later.

    It would be good to tackle shadow-cljs next, which provides a seamless transition to using npm modules (which most JS devs should be familiar with) as well as hot code reloading, which is the killer app of CLJS.

    1. Thank you! What do you mean by “JS will need to keep an open mind though about all these dependencies” — that existing JS users might be put off by the weight of GCL etc?

      I’m planning to have a go at `shadow-cljs` in a future post :).

  2. Hello and thank you for the post, it’s really great intro 🙂

    What would be the optimal path to learn ClojureScript in your opinion?

Leave a comment

Your email address will not be published. Required fields are marked *