In this first tutorial we are going to create and configure a minimum ClojureScript (CLJS) project by using the boot build tool.
This tutorial requires java
and boot
to be installed on your
computer.
To install java
follow the instructions for your operating
system. To install boot
follow the very easy instructions in the
corresponding section of its README.
Test the installation by issuing the boot -h
command at the
terminal. Then submit the boot -u
command to get the latest boot
updates.
NOTE 1: I strongly suggest to use Java 8. If you're using Java 7, it might be worth mentioning https://github.com/boot-clj/boot/wiki/JVM-Options#permgen-errors
A minimum CLJS web project is composed of 3 files:
- an html page;
- a CLJS source code;
- a
boot
build file to compile CLJS source code.
Even if CLJS does not dictate a specific directory structure, it's a good practice to organize your project in such a way that it will be easy for anyone, even yourself in a few months, to be able to understand the project components and its structure. Moreover, each building tool has its own idiosyncrasies, which they call defaults. The more you adhere to the defaults of the tool at hand, the less pain you will experience while managing the project.
Taking these premises into account, let's create a directory
structure for our new project, named modern-cljs
, by adhering as
much as possible to the boot
defaults.
The suggested file layout of the project is the following:
modern-cljs/
├── build.boot
├── html
│ └── index.html
└── src
└── cljs
└── modern_cljs
└── core.cljs
modern-cljs
is the home directory of the project;src/cljs/
hosts CLJS source files;html
hosts html resources;
NOTE 2: Single segment namespace are discouraged in CLJ/CLJS. That's why we created the
modern_cljs
directory name. Due to Java difficulties in managing hyphen "-" (or other special characters) in package names, we substituted an underscore (_
) for any hyphen (-
) in corresponding directory names.
NOTE 3: Please note that the filename extension for ClojureScript sources is cljs, not clj.
Issue the following command at the terminal:
mkdir -p modern-cljs/{src/cljs/modern_cljs,html}
Let's now create the three needed files by issuing the folowing command:
cd modern-cljs
touch html/index.html src/cljs/modern_cljs/core.cljs build.boot
We're now going to write our very first CLJS code. Open the
src/cljs/modern_cljs/core.cljs
file with your preferred editor and
type into it the following CLJS code:
;; create the main project namespace
(ns modern-cljs.core)
;; enable cljs to print to the JS console of the browser
(enable-console-print!)
;; print to the console
(println "Hello, World!")
Every CLJ/CLJS file must start with a namespace declaration matching a path on your disk:
modern-cljs.core
<--> modern_cljs/core.cljs
The (enable-console-print!)
expression redirects any printed output
to the console of the browser in such a way that the (println "Hello, world!")
prints Hello, World!
to the console.
We now need a way to compile core.cljs
to JS and link it to the
index.html
page.
First, open the file html/index.html
and edit it as follows:
<!doctype html>
<html>
<head>
<title>Hello, World!</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
Note that there are no references to CLJS files. We only added a
<script>
tag to link the index.html
page to the main.js
file. This JS file will be generated by the boot
building tool while
compiling the above core.cljs
source code.
To compile the core.cljs
file, we need to configure the boot
command by
editing the build.boot
file, which is just a regular CLJ file with a
different extension:
(set-env!
:source-paths #{"src/cljs"}
:resource-paths #{"html"}
:dependencies '[[adzerk/boot-cljs "1.7.228-2"]])
(require '[adzerk.boot-cljs :refer [cljs]])
Pretty minimal! The set-env!
function sets :source-paths
and
:resource-paths
options to the corresponding values of the project
structure as we have created above. Then it injects the boot-cljs
compilation task as the only explicit dependency of the project by
adding it to the :dependencies
keyword.
Note that even if we did not include any clojure
and clojurescript
dependencies, boot
will be able to automagically download the
corresponding releases it knows to work well with it.
Finally, the require
form makes the cljs
task visible to the
boot
command.
If you now run the boot -h
command from the terminal, you'll see
that the cljs
task is now available to boot
.
boot -h
...
Tasks: ...
target Writes output files to the given dir...
...
zip Build a zip file for the project.
cljs Compile ClojureScript applications.
...
Do `boot <task> -h` to see usage info and TASK_OPTS for <task>.
You now may want to have more information about the cljs
task by
issuing the following command:
boot cljs -h
Compile ClojureScript applications.
...
Available --optimization levels (default 'none'):
...
As you see, the default optimization directive for the CLJS compiler
is none
. Another tutorial will explain the different CLJS compilation
optimizations (i.e. none
, whitespace
, simple
and advanced
). At
the moment stay with none
, which is commonly used during development
cycles. Let's see boot cljs
at work:
boot cljs
Writing main.cljs.edn...
Compiling ClojureScript...
• main.js
The cljs
task compiled your CLJS code by producing a main.js
JS
file and you now know why we called the JS file included in the
<script>
tag of the index.html
page main.js
: just to adhere to
an easy default.
That said, if you look for it the directory structure of the project, you'll be surprised to not find it:
tree
.
├── build.boot
├── html
│ └── index.html
└── src
└── cljs
└── modern_cljs
└── core.cljs
4 directories, 3 files
The fact is that by default boot
does not create an output file if
you don't explicitely instruct it to do so. Let's see the command line
help for the target
predefined task:
boot target -h
Writes output files to the given directory on the filesystem.
Options:
-h, --help Print this help info.
-d, --dir PATH Conj PATH onto the set of directories to write to (target).
-m, --mode VAL VAL sets the mode of written files in 'rwxrwxrwx' format.
-L, --no-link Don't create hard links.
-C, --no-clean Don't clean target before writing project files.
Interesting. The target
predefined task of boot
is able to write
the output of a task (e.g. cljs
) to a given directory. The above
help doesn't tell you, but if you do not specify a directory it writes
the output file to the a default target
subdirectory of your project's home
directory, as you can verify as follows:
boot cljs target
Writing main.cljs.edn...
Compiling ClojureScript...
• main.js
Writing target dir(s)...
tree
.
├── boot.properties
├── build.boot
├── html
│ └── index.html
├── src
│ └── cljs
│ └── modern_cljs
│ └── core.cljs
└── target
├── index.html
├── main.js
└── main.out
├── boot
│ └── cljs
│ ├── main312.cljs
│ ├── main312.cljs.cache.edn
│ ├── main312.js
│ └── main312.js.map
├── cljs
│ ├── core.cljs
│ ├── core.js
│ └── core.js.map
├── cljs_deps.js
├── goog
│ ├── array
│ │ └── array.js
│ ├── asserts
│ │ └── asserts.js
│ ├── base.js
│ ├── debug
│ │ └── error.js
│ ├── deps.js
│ ├── dom
│ │ └── nodetype.js
│ ├── object
│ │ └── object.js
│ └── string
│ ├── string.js
│ └── stringbuffer.js
└── modern_cljs
├── core.cljs
├── core.cljs.cache.edn
├── core.js
└── core.js.map
17 directories, 27 files
A lot of stuff. We're not digging into it right now. At the moment we're only interested in noting a few things:
- the original directory structure living in
html
andsrc
is untouched - everything, even the
index.html
resource, has been generated into the newtarget
directory.
Considering that boot
is under continuous development, I strongly
suggest you to pin the current stable release in your project by creating a new
boot.properties
file as follows:
boot -V > boot.properties
which should now have the following content:
cat boot.properties
#http://boot-clj.com
#Thu Mar 09 14:56:52 CET 2017
BOOT_CLOJURE_NAME=org.clojure/clojure
BOOT_CLOJURE_VERSION=1.7.0
BOOT_VERSION=2.7.1
boot
uses a pretty neat approach in taking apart the input of the
project from the corresponding output generated by its tasks. You'll
never see an input file from the :source-paths
and the
:resource-path
original directories be modified by any boot
task. Aside from internally generated temporary directories,
everything happens in the explicit target
directory.
Open a browser and visit the local target/index.html
file. Now open
the console in your Development Tool (e.g. Chrome Development Tool).
If everything went ok, you should see "Hello, World!" printed at the
console.
Next Step - Tutorial 2: Immediate Feedback Principle
In the next tutorial we're going to adhere as closely as possible to the Bret Victor Immediate Feedback Principle to build a very interactive development environment.
Copyright © Mimmo Cosenza, 2012-2015. Released under the Eclipse Public License, the same as Clojure.