Hello world in ClojureScript with clj and deps.edn

Back to SIG ClojureScript Tutorials

Author: Gary Johnson

Date: 2021-07-08

clj is a shell script that provides an interface to several functions within the clojure JAR file. The first time you run it, clj has to download the clojure JAR and store it in your $HOME/.m2/repository directory tree for later use.

Every time you run clj, it will check the current directory to see if there is a deps.edn file present. If so, it will read the :deps map from it to determine which dependency libraries need to be made available on your JVM classpath. Any which have not already been downloaded to $HOME/.m2/repository will be downloaded at this point and saved for later use.

Note, in particular, that to compile clojurescript to javascript, you must include the clojurescript JAR in your deps.edn file. This will ensure that it is downloaded to $HOME/.m2/repository and added to your classpath.

At a minimum, you will need to add this to your project's deps.edn file:

{:paths ["src/cljs"]
 :deps  {org.clojure/clojure       {:mvn/version "1.10.3"}
         org.clojure/clojurescript {:mvn/version "1.10.866"}}}

Next, create the file src/cljs/hello_world/core.cljs, containing this code:

(ns hello-world.core)

(defn say-hello []
  (js/alert "Hello from CLJS!"))

(say-hello)

At this point, your directory tree should look like this:

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

Now you are ready to compile core.cljs into javascript. Run the following command from your shell:

clj -M --main cljs.main --compile hello-world.core --repl

This will read deps.edn, make sure you have the clojure and clojurescript JARs in $HOME/.m2/repository (or download them if not), spin up a JVM with src/cljs and both JARs on its classpath, load the cljs.main namespace (from the clojurescript JAR), and run its -main method with ["--compile" "hello-world.core" "--repl"] as its arguments. This -main method is the entrypoint function for the clojurescript compiler, which then loads the hello-world.core namespace (found under src/cljs/hello_world/core.cljs <-- note that "-" in namespaces becomes "_" in filenames). The clojurescript compiler then compiles your code into javascript under the out/ directory by default. Finally, since you passed the "--repl" argument, a web browser window will be opened and pointed to http://localhost:9000. This loads up a default webpage provided by the clojurescript JAR file, that contains javascript code to connect back to your REPL, so that the browser can act as the runtime environment for your javascript code as well as any forms that you type at the REPL.

Since the code in core.cljs included a call to (say-hello) at the toplevel, this function will be run as soon as the page loads, which will display a javascript alert box in your browser window with the text "Hello from CLJS!" in it.

To verify that everything is working correctly, you'll want to use require and in-ns to load and navigate to your hello-world.core namespace in the REPL. Then you can run the (say-hello) function again interactively. Here's the command sequence you want to type:

ClojureScript 1.10.866
cljs.user=> (require 'hello-world.core)
nil
cljs.user=> (in-ns 'hello-world.core)
nil
hello-world.core=> (say-hello)
nil

If everything works correctly, you'll see the familiar javascript alert box pop up in your browser window with the text "Hello from CLJS!" in it.

Now, although this shows that your clojurescript to javascript compilation is working correctly, it doesn't provide a straightforward path to simply copy your javascript into a separate web application and load it. That's because in order for your say-hello function to work, you need to include not only your generated javascript (from hello-world.core) but also all the javascript for the clojurescript and google closure systems that are the necessary dependencies here. If you look at out/main.js, you'll see the javascript code that your browser window loads when it starts up the browser REPL page. This code injects script tags into the page to load the clojurescript and google closure dependencies, then the browser repl code, and finally your hello_world.core javascript module (note again that javascript module names use "_" rather than "-" as in clojurescript).

If you look in out/hello_world/core.js, you'll see just the javascript that your clojurescript file generated:

goog.provide('hello_world.core');
goog.require('cljs.core');
hello_world.core.say_hello = (function hello_world$core$say_hello(){
return alert("Hello from CLJS!");
});

What you'll need to do for this simple practice example is to tell the clojurescript compiler to combine all of the javascript that was generated from your hello_world/core.cljs file together with all of its dependencies into a single file, prune all of it to remove everything that you don't absolutely need to make your code work, minify all the variable and function names, and remove the whitespace, so it loads quickly. This set of steps is what we call "advanced mode compilation". You can enable it with this command:

clj -M --main cljs.main --optimizations advanced --output-to app.js --compile hello-world.core

When this command finishes, you'll find a file called app.js in the current directory. Make a file called index.html in the current directory containing this code:


    
        CLJS Test Page
        
    
    
        Did you see an alert box?
    

Now simply open index.html in your web browser, and you'll see the same alert box as before pop up with the text "Hello from CLJS!" in it.

Hopefully, that will give you the tools you need to get your feet wet with clojurescript programming. You can see all the command line options for the clojurescript compiler by running this command:

clj -M --main cljs.main -h

Also, please keep in mind that clojurescript developers don't usually work directly at the command line like this. Instead, they would usually use a tool like figwheel-main or shadow-cljs to automate the compilation process and hot-load live code changes into your web browser during development.

Welcome again to the wonderful world of clojure and clojurescript programming. Have fun and happy hacking!