-
Notifications
You must be signed in to change notification settings - Fork 790
Quick Start
NOTE: This wiki page is under development for ClojureScript >= 0.0-3030. Thanks for your patience.
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.
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.
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
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 out 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
(the name doesn't matter),
and 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.
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.
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 browser like Chrome require you to
first enable source mapping, for more details
look here.
The previous section explained some important fudamental concepts
around the Google Closure Library. Howevr 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.
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.
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 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, if it doesn't connect immediately try
refreshing the browser again. Try evaluating a simple expression
like (+ 1 2)
.
Run tail -f out/watch.log
in a fresh terminal to view auto build
progress.
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 nodehello
(:require [cljs.nodejs :as nodejs]))
(defn -main [& args]
(println (apply str (map [\ "world" "hello"] [2 0 1]))))
(nodejs/enable-util-print!)
(set! *main-cli-fn* -main)
For a full list of compiler options, see Compiler Options.
- Rationale
- Quick Start
- Differences from Clojure
- [Usage of Google Closure](Google Closure)