This repository is no longer actively maintained. Please use or create a fork.
node-clojurescript aims to provide seamless integration between NodeJS and ClojureScript. This is a young project, started in May 2012, it's under active development and welcomes participation by the NodeJS and Clojure communities.
The ClojureScript library ships with some basic mechanisms for creating compiled scripts suitable for use with NodeJS. But more is possible, and this library aims to make compiling, loading and running
.cljs scripts a breeze, in the same manner that CoffeeScript
.coffee files can be used transparently in the development of NodeJS-backed applications.
Want to get started? There are some prerequisites, but if you'd prefer to trouble with those later:
$ npm install -g clojure-script
If you get an error message from npm, it means you need to review the Prerequisites section of this README.
$ touch hello.cljs && vim hello.cljs
Now paste in something like:
(ns hello (:require [cljs.nodejs :as nodejs])) (defn greet [n] (println (str "Hello, " n))) (nodejs/next-tick (fn  (greet "World!")))
Save it, and leave the editor open. In another terminal, navigate to the directory where you created
hello.cljs and do:
$ ncljsc hello.cljs
When you invoke
ncljsc, it fires up the JVM, the ClojureScript compiler and the Google Closure Compiler. This means the compilation will seem slow, even really slow (10+ seconds), especially if you're used to the sub-second compile times of CoffeeScript. That's expected, and the issue will be revisited in the Faster, faster! section below.
You should eventually see printed in your terminal:
$ ncljsc hello.cljs Hello, World!
Now replace the contents of
Save it, then rerun
ncljsc hello.cljs and point your browser to localhost:4200.
Did it work? Cool! (maybe submit an issue if it didn't)
The slow compile times mentioned above are owing to startup time of the JVM, plus the time to initially load the two underlying compilers (ClojureScript and Google Closure). This is an annoying problem...
Problem solved! Starting with
v0.1.4, node-clojurescript offers a way to compile against a long-running, "detached" JVM server:
$ ncljsc --server 4242
Invoke the command above and leave the terminal open (or run it in a tmux or screen session). You don't need to navigate to a particular path before starting it, bu you need to leave it running. After 10+ seconds you should see:
$ ncljsc --server 4242 Starting up, please wait... Initial build completed, JVM and compiler are primed and ready! Detached JVM server listening at http://127.0.0.1:4242/
If you see something about a
DTraceProviderBindings error, just ignore it as it's harmless. Depending on your platform, no error may be reported.
Now open another terminal and go back to the directory where you created
hello.cljs. Then do:
$ ncljsc --client 4242 hello.cljs
You should notice a marked difference in the time required for the script to run. Once the
--server JVM is "hot", compile times should take only a few seconds, instead of 10+ seconds. That's because the
--client process does not start its own JVM.
--server process accepts "build requests" over HTTP, listening on
localhost at the specified port. The
--client then makes synchronous or asynchronous requests (depending on how it's invoked). And that's it: from the perspective of the end-user, the only difference is that these "remote" builds happen more quickly than "local" builds. Overall usage of
ncljsc is the same whether you run remote or local builds.
- This feature is under active development and won't always work correctly, e.g. errors may not always make it back to the client and the return value of a build may be an empty string. It's best to keep an eye on the terminal output of the server process for signs of trouble.
- You may use whatever port number you prefer, as long as the client and server use the same port.
- Requests are restricted to the
- There is a transparent exchange of credentials hard-wired into the client-server logic, so that arbitrary processes can't make build requests. (security)
- Aforesaid "credentials exchange" requires server and client processes to be run as the same user. (security)
- Credentials aren't persistent, so if the server process is bounced (you restart it, maybe it crashed), client processes must be restarted if they're long-running and will attempt further build requests. (security?)
- Don't use the client-server mode in a production environment. (goes without saying?)
It's 2012 and you shouldn't have to manually re-run your scripts while you're developing them. And you don't!
After some experimentation, supervisor seems (to the author) to be the simplest and most flexible NodeJS-based tool for automatically re-starting scripts in a development workflow. Make sure to install it globally:
npm install -g supervisor.
supervisor installed and a
ncljsc --server process running, revisit the directory where you created
hello.cljs and do:
$ supervisor -w hello.cljs -n exit -x ncljsc -- --client 4242 hello.cljs
That's a lot of flags for a single command, but see
supervisor --help and you'll soon have the hang of it. Note that we're making use of
--client 4242, which is proper to
hello.cljs and watch what happens when you save it. Fantastic! It compiles quickly, and will do so repeatedly whenever you save changes, so long as you keep
In addition to running
hello.js in the same directory (local build)
$ ncljsc --compile hello.cljs
myscript/hello.js in the same directory (remote build)
$ ncljsc --compile --output myscript --client 4242 hello.cljs
re-compiles and re-saves when changes are made (remote build)
$ supervisor -w hello.cljs -n exit -x ncljsc -- --client 4242 --compile hello.cljs
ncljsc command provides additional capabilities. Try:
$ ncljsc --help
Not all of the features have been implemented yet. Also, you'll notice that
ncljsc provides built-in
--watch-deps options. Those do work, but there are some outstanding bugs related to NodeJS's
fs.watch facility. As such, it seemed better to propose
supervisor as a file watching tool than explain a bunch of caveats regarding the built-in watch support. But by all means experiment and report back.
So now what you should do is read up on Clojure and ClojureScript and get to busy! See the Resources section below.
This library wraps a NodeJS front-end around the ClojureScript compiler, which is written in the Clojure language, which is hosted on the Java Virtual Machine (JVM). That means you must have Java setup to successfully install
clojure-script with npm.
You'll also need to export the proper value for
JAVA_HOME into your environment. The installation instructions for the
node-java package are quite helpful in this regard, though note that installing
clojure-script with npm will automatically install
node-java as well (i.e. you don't need to do that separately).
If you're new to NodeJS and don't have it setup, that will be your next step. I highly recommend Tim Caswell's Node Version Manager (nvm). It's easy to install and, and makes working with multiple
node versions dead simple. For example:
$ nvm install v0.6.17 ... $ nvm install v0.6.10 ... $ nvm use v0.6.17
When you install
npm tool will get installed along with it. So as long you have Java in place (see above), you should be ready to run:
$ npm install -g clojure-script
That's it! Installing
clojure-script (the npm package name for this library) will automatically perform a package-localized installation of Clojure, ClojureScript, Google Closure Compiler, etc.
If you get an error during installation, look closely at the error message. Maybe you made a typo while following the steps above? If you can't figure it out, feel free to submit an issue.
With a local (vs. global)
node_modules installation of
clojure-script, you can load
.cljs modules from other scripts without having to compile them beforehand.
With respect to the Quick Start examples above: create a script
other.js in the same directory as
hello.cljs, then paste in the following and save it:
require('clojure-script'); require('./hello'); // or require('./hello.cljs');
Then you can do:
$ node other.js
It's entirely possible to leverage the
require support in combination with node-clojurescript's client-server mode described in the Faster, faster! section above.
Suppose you have a "detached" JVM server process running on port
$ ncljsc --server 8888
.js script you can then indicate:
In this context, the client-logic makes a synchronous (not async) build request against the server process.
hello.cljs will be transparently compiled and loaded as before, but more quickly.
You can call the function returned by
require without arguments, like so:
In that case, the port number will default to
4242 (make sure the server process is using the same port). Note that calling the function without arguments and not calling it are two distinct things. If you don't call it, the
clojure-script module will start a new JVM and perform a local build. If you do call it, with or without arguments, a JVM will not be started and the module will make a remote build request.
Clojure and ClojureScript support the notion of namespaces. Unlike loading modules with NodeJS's
require, using ClojureScript's namespace
Try creating two scripts,
(ns foo (:require [cljs.nodejs :as nodejs] [bar :as bar])) (defn ^:export greet [name, title] (str "Hello, " (bar/title title) " " name))
(ns bar (:require [cljs.nodejs :as nodejs])) (defn ^:export title [t] (str t " Amazing" ))
Now create a third script,
var foo = require('./compiled.js').foo; console.log(foo.greet('ClojureScript developer!', 'Mr.'));
Time to compile:
$ ncljsc -c -p foo.cljs > compiled.js
When that's finished, it's time to run
$ node greet.js Hello, Mr. Amazing ClojureScript developer!
Examining the plentiful contents of
compiled.js, you'll see (toward the bottom) that both
bar.cljs were compiled into the stand-alone JS file.
Leiningen is a popular and flexible build tool in wide use among Clojure developers. node-clojurescript bundles the shell script front-end to Leiningen (the
lein command) and proxies to it with an executable script named
If you've installed the
clojure-script module globally with
npm install -g clojure-script, then you should be able to run:
nlein is a simple proxy script and does not feature any customizations of Leiningen. If you already have a
lein executable on your path,
nlein will ask whether it should delegate to it, with the option to remember your decision.
nlein, when it's not delegating to another
lein, will store JAR files and other things it downloads in the
support/.lein directory, relative to the root of the
clojure-script package. This is to keep
nlein from even potentially conflicting with an existing installation. Normally,
lein stores such things in
All in all, the purpose of
nlein is to provide an easy way for NodeJS developers to get up and running with Leiningen. If you're already using Leiningen, you may choose to ignore
nlein and go about your business as usual.
There are several goals that need to be accomplished in short order:
- The tooling developed in CoffeeScript needs to be re-implemented in ClojureScript so that this library will be pseudo self-hosting.
- A plugin for the Leiningen build tool needs to be adapted or written, for use in development of complex ClojureScript projects in conjunction with this library and other NodeJS modules.
- Missing features of
ncljscneed to be implemented, the most important being a REPL.
- More and better documentation and examples.
Help in accomplishing these and future goals is more than welcome.
$ ncljsc --help
#node.js channels on Freenode IRC.
This software is derived from and incorporates existing works:
In particular, many thanks are owed to Jeremy Ashkenas and the other CoffeeScript maintainers. Using the CoffeeScript tooling as a template, it was possible to whip together a useable NodeJS front-end in one intense week. It would have otherwise been much more slow-going.
This software is Copyright (c) 2012 by Michael Bradley, Jr.
The use and distribution terms for this software are covered by the Eclipse Public License 1.0 which can be found in the file epl-v10.html under the licenses directory at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.