-
Notifications
You must be signed in to change notification settings - Fork 789
Quick Start
The only dependencies required for this tutorial are 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.
Dear Reader: Do not edit this page without first consulting an experienced member of the ClojureScript community. This is one of the most important resources for beginners and it has been extensively verified on a variety of environments. If you believe you have found a mistake, there's a 99.9% chance you have not found a mistake. If you truly believe you have found an error in this document, please come find help on the IRC or Slack channels and we can help determine if this page needs edits. Thank you.
The standalone ClojureScript JAR comes bundled with Clojure 1.7.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.
Debug Note: If you use
curl
to download the JAR, be sure to specify the-L
flag to follow redirects. (Otherwise you'll end up with an HTML page of about 300 bytes) Make sure the file is not renamed by your browser, eg. cljs(1).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:
md src\hello_world & type nul >>core.cljs & move core.cljs src\hello_world
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 enable
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.build.api)
(cljs.build.api/build "src" {:output-to "out/main.js"})
We require
the cljs.build.api
namespace. We then invoke the
standard function for building some ClojureScript source -
cljs.build.api/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
On Windows:
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.
Debug Note: If you encounter an error about
cljs.build.api
not being found, make sure that you didn't write(require 'cljs.build.api')
, many popular text editors will emit a matching single quote even in their Clojure mode.
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.
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. (Note that nothing will appear in the main browser window—no content is being rendered.)
You will not see "Hello world!"
but 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.
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");
// Note the underscore "_"!
</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).
The previous section explained some important fundamental concepts
around the Google Closure Library. However it also involved a
substantial amount of boilerplate. We can eliminate this boilerplate
by specifying a :main
entry point in the options that we pass to
cljs.build.api/build
. Let's do that now:
(require 'cljs.build.api)
(cljs.build.api/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 on Mac or GNU/Linux:
java -cp cljs.jar:src clojure.main build.clj
On Windows:
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. The previous contents of main.js
are now in out/cljs_deps.js
, which is loaded alongside our namespace by the new out/main.js
.
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.build.api)
(cljs.build.api/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.
Terminate auto building (using Ctrl-C
) before proceeding to the next section.
Further Reading: While not required for the remainder of the Quick Start, it's highly recommended that you familiarize yourself with basics of Google Closure Library. Many simple errors can be avoided by reinforcing your understanding of how Closure Library works.
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.build.api)
(require 'cljs.repl.browser)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "out/main.js"
:verbose true})
(cljs.repl/repl (cljs.repl.browser/repl-env)
:watch "src"
:output-dir "out")
We build the project at least once before constructing the REPL.
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.build.api/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
specify :output-dir
so that the REPL can reuse compiled files
generated by the build.
We also need to modify our source 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 build. You will also see innocuous WARNING
s 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. (Note that the REPL will appear in your terminal, not in the browser.)
Try evaluating a simple expression like (+ 1 2)
.
Debug Note: If the REPL doesn't connect immediately try refreshing the browser a few times (Chrome & Firefox tend to be more stable than Safari). Note that eval will be slow in some browsers if you don't have the browser REPL tab focused. If for some reason the REPL completely hangs, just refresh the page.
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!")
;; ADDED
(defn foo [a b]
(+ a b))
At the REPL prompt, 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 new definition in our REPL by appending a :reload
keyword
to our require statement thereby forcing 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.
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 optimizations, 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.build.api)
(cljs.build.api/build "src"
{:output-to "out/main.js"
:optimizations :advanced})
(System/exit 0)
Under :advanced
optimizations :main
is not needed as advanced
compilation creates a single JavaScript artifact. We also add a
(System/exit 0)
as the Google Closure Compiler creates a thread pool
that isn't shutdown - we know we're done we can just exit.
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
implicit dependencies on the entire ClojureScript standard library
(10KLOC) and the Google Closure Library (300KLOC). You can thank
dead code elimination.
First install Node.js. 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. Your src/hello_world/core.cljs
should look like the following:
(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.build.api)
(cljs.build.api/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 project:
java -cp cljs.jar:src clojure.main node.clj
You can run your file with:
node main.js
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.build.api)
(require 'cljs.repl.node)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "out/main.js"
:verbose true})
(cljs.repl/repl (cljs.repl.node/repl-env)
:watch "src"
:output-dir "out")
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.
ClojureScript supports a wide variety of options for including ClojureScript and JavaScript dependencies (see Dependencies for details). However the simplest approach is to include a properly packaged JAR on the classpath. CLJSJS provides a nice set of curated JavaScript libraries that suffices to demonstrate how dependencies are handled.
React is a popular dependency for ClojureScript projects. CLJSJS provides a bundled version. Let's see how to include it.
Grab the JAR from Clojars:
curl -O https://clojars.org/repo/cljsjs/react/0.12.2-8/react-0.12.2-8.jar
Let's edit our simple program to look like the following so that React is properly required:
(ns hello-world.core
(:require cljsjs.react))
(enable-console-print!)
(println "Hello React!")
Let's rebuild our project, all we need to do is extend our classpath to include the CLJSJS React JAR.
java -cp cljs.jar:src:react-0.12.2-8.jar clojure.main build.clj
If you refresh your index.html
page you should see the usual React
log indicating that React was successfully loaded.
If you have a few dependencies, one convention is to put them into
a folder called lib
. Then you can launch your scripts like so:
java -cp 'cljs.jar:lib/*:src' clojure.main build.clj
As your dependency graph becomes more sophisticated it may make sense to rely on Maven or Leiningen to manage dependencies for you. Please refer to Dependencies for a comprehensive tutorial. What follows is just the basics.
All the commands above may be executed with the run
feature of
Leiningen. With Leiningen you do not need to specify the classpath.
For example for a REPL you would do something like the following from
your project directory:
lein run -m clojure.main repl.clj
One important thing to remember with Leiningen is the :source-paths
configuration parameter. The :source-paths
, found in the project.clj,
are appended to the classpath. For a ClojureScript build you should
include all the source paths to your ClojureScript sources in the
:source-paths
parameter. The path "src" is included by default.
This is especially important if you use another path besides "src".
If you have any questions about the classpath you can see it easily by typing
lein classpath
.
Same as Leiningen, you can use Maven to run the above scripts and you
do not need to specify the classpath. Add the clojure-maven-plugin
to
your pom.xml
:
<project xmlns="..."
xsi:schemaLocation="....">
<modelVersion>4.0.0</modelVersion>
...
<build>
<plugins>
<plugin>
<groupId>com.theoryinpractise</groupId>
<artifactId>clojure-maven-plugin</artifactId>
<version>1.7.1</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
...
</project>
Then you can start a repl with the clojure:run
task:
mvn clojure:run -Dclojure.script=repl.clj
- Rationale
- Quick Start
- Differences from Clojure
- [Usage of Google Closure](Google Closure)