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.
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 # (macOS/Linux only) A file for listing our dependencies
If you are on macOS or Linux your deps.edn
file should contain the following:
{:deps {org.clojure/clojurescript {:mvn/version "1.11.54"}}}
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.
Make sure you are in the hello-world
project directory and run the following
command:
clj -M --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:
If you are running Linux and the REPL does not start, try disabling browser
auto-launch and opening http://localhost:9000
manually:
clj -M --main cljs.main --repl-opts "{:launch-browser false}" --compile hello-world.core --repl
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.
clj -M -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.
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 -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 -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.
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. We’re also using --output-to
here for specifying the --output-to
file:
clj -M -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
|
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, |
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 -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.
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.11.54"}
cljsjs/react-dom {:mvn/version "18.3.1-1"}}}
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 -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!
.