ClojureScript workflow with Clojure CLI tools and Figwheel-main
Getting started with ClojureScript development by creating a new project, using Figwheel-main build tool and Rebel rich terminal UI.
The figwheel-main template creates a project with Clojure CLI configuration, providing example code and build configurations for development, testing and deployment workflows which are explored in some detail.
Updated from an article first written in 2019 and originally covered in Practicalli Clojure study group #38 video
Creating projects from templates
Previous articles used clj-new to create projects using a variety of templates.
clj-new uses the Leiningen template format and some template developers will include project configuration for Clojure CLI. Each template designer is responsibility as to which project and build tools it supports.
The figwheel-main template provides an option to create a Leiningen or Clojure CLI project and clj-new will create a Clojure CLI configuration by default.
Where a template only provides a Leiningen configuration, dependencies listed in
project.clj
should be added to the:deps
section ofdeps.edn
.dev-dependencies
inproject.clj
should be satisfied by aliases in the project or user-leveldeps.edn
configuration.
ClojureScript project using figwheel
Use the figwheel-main template to create a ClojureScript project that uses figwheel-main to manage the build.
In a terminal, use the following command:
clojure -M:project/new figwheel-main practicalli/hello-world -- --reagent
The
:project/new
alias is defined in practicalli/clojure-deps-edn user-level configuration and supports-M
,-X
andT
execution flags. The-X
and-T
flags use a command with key value arguments
clojure -T:project/new :template figwheel-main :name practicalli/landing-page :args '["+reagent"]'
The practicalli/hello-world
defines the main application namespace as hello-world
and practicalli
as the domain.
--
after the name tells clj-new to pass the following text to the template.
--reagent
is a template option to add reagent React-style library dependencies to the generated project. --rum
and --react
are other React-style libraries that could be used instead of --reagent
Project configuration
A new project is created in the hello-world
directory and contains a deps.edn
configuration
{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
org.clojure/clojurescript {:mvn/version "1.11.4"}
cljsjs/react {:mvn/version "17.0.2-0"}
cljsjs/react-dom {:mvn/version "17.0.2-0"}
reagent/reagent {:mvn/version "1.1.1" }}
:paths ["src" "resources"]
:aliases {:fig {:extra-deps
{com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
org.slf4j/slf4j-nop {:mvn/version "1.7.30"}
com.bhauman/figwheel-main {:mvn/version "0.2.17"}}
:extra-paths ["target" "test"]}
:build {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}
:min {:main-opts ["-m" "figwheel.main" "-O" "advanced" "-bo" "dev"]}
:test {:main-opts ["-m" "figwheel.main" "-co" "test.cljs.edn" "-m" "practicalli.test-runner"]}}}
Aliases were added by the template to run figwheel and build the ClojureScript code:
:fig
adds figwheel-main and rebel-readline libraries as dependencies, slf4j-nop provides a no-operation logger (suppresses default logger warning):build
runs figwheel-main which generates JavaScript from the ClojureScript code in the project:min
creates a minified single JavaScript file for deployment:test
runs all tests undertests/practicalli
directory using the figwheel-main test-runner
dev.cljs.edn
is the build configuration referred to by the :build
and :min
aliases using the dev
name
^{:watch-dirs ["test" "src"]
:css-dirs ["resources/public/css"]
:auto-testing true
}
{:main practicalli.hello-world}
:watch-dirs
defines the directories to monitor files for saved changes:css-dirs
defines the location of the CSS configuration:auto-testng
to automatically discover and run tests:main
defines the main namespace for the application
Check figwheel configuration
Ensure figwheel is configured correctly by calling figwheel.main
without a build configuration. The ClojureScript code for the project is not compiled so cannot be the cause of any failure or warnings.
This is quick way to identify if issues are from figwheel configuration or from ClojureScript code.
clojure -M:fig -m figwheel.main
A web browser window will open showing the figwheel website, contains the fundamental documentation for developing with Figwheel.
Rich REPL UI with Rebel
Figwheel will run Rebel readline to start the REPL, as the :fig
alias includes com.bhauman/rebel-readline-cljs
as an extra dependency.
Rebel provides syntax highlighted code, auto-completion, commands to manage the REPL and Clojure documentation help, all within its rich command line.
Typing :repl/help
as a command at the Rebel prompt shows characters are syntax highlighted. The command provides a quick reference for Rebels capabilities.
Evaluate expressions by typing them at the Rebel prompt and pressing RET
, e.g. (map inc [2 4 6 8])
JavaScript interop code also works from the Rebel prompt, e.g. (js/alert "Notification from the command line")
will display an alert in the browser.
Practicalli Clojure provides examples of using Rebel as a rich terminal UI for the Clojure REPL
The
clojure
command should be used to run Rebel. Theclj
wrapper script callsrlwrap
which conflicts with Rebel, as they are both readline tools.
Running figwheel and building the project
Calling figwheel with a build configuration compiles the project ClojureScript code into JavaScript as figwheel starts. The JavaScript code is sent to the JavaScript engine in the browser window that figwheel opened.
Saved changes to the project ClojureScript files will automatically generate updates to the JavaScript code and send them to the JavaScript engine in the browser.
The :build
alias is used during development of a ClojureScript project
clojure -M:fig:build
The
:build
alias definesfigwheel.main
as the main namespace and the arguments passed to the-main
function in that namespace."-b" "dev"
will useddev.cljs.edn
as the configuration,-r
option to run a REPL prompt (in this case using Rebel)
:build {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}
This configuration is the equivalent of running the command
clojure -M:fig -m figwheel.main -b dev -r
Run Figwheel-main from Emacs Cider
Figwheel-main projects can be run from Emacs with CIDER using the cider-jack-in-cljs
command. The user is prompted for the build name to use.
Emacs CIDER Jack-in with Clojure CLI projects benefits from a .dir-locals.el
file to set the :fig
alias (and any other aliases) and the figwheel build configuration when starting the Clojure REPL.
((clojure-mode . ((cider-preferred-build-tool . clojure-cli)
(cider-clojure-cli-aliases . ":fig")
(cider-default-cljs-repl . figwheel-main)
(cider-figwheel-main-default-options . "dev")
(cider-repl-display-help-banner . nil))))
Use
cider-connect-cljs
to connect Emacs to a REPL (nREPL) process that is already running, i.e. via theclojure -M:fig:build
command in the terminal.
Running tests once
The test
directory contains source code for unit tests, using a directory path matching the namespace they are testing from the src
directory. -test
is added to the end of the test namespaces. For example, if we have a source namespace of practicalli.hello-world
, the tests would be in practicalli.hello-world-test
.
Use the :test
alias with the :fig
alias to run the Figwheel test runner
clojure -M:fig:test
This will open a browser and connect to its JavaScript REPL and run the tests.
The results of the test run are printed to the terminal
Running tests this way may not be as fast as using the continuous testing approach (covered next)
test configuration
The :test
alias uses the test.cljs.edn
build configuration to start the Figwheel test runner and runs all the test namespaces under the test
directory.
:test {:main-opts ["-m" "figwheel.main" "-co" "test.cljs.edn" "-m" practicalli.test-runner]}
The test.cljs.edn
build configuration defines a separate URL to open the test host page, to avoid clashing with the URL to connect to the ClojureScript application itself.
^{
;; alternative landing page for the tests to avoid launching the application
:open-url "http://[[server-hostname]]:[[server-port]]/test.html"
;; launch tests in a headless environment - update path to chrome on operating system
;; :launch-js ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" "--headless" "--disable-gpu" "--repl" :open-url]
}
{:main practicalli.test-runner}
Continuous testing during development
Running a figwheel-main build includes continuous testing service, so you can instantly see the results of your tests once the application has started.
clojure -M:fig:build
http://localhost:9500/figwheel-extra-main/auto-testing will show the live results of running the tests.
You may see the auto-testing host page display before showing the test results, or if the web page is reloaded (or if your tests take a long time to run or there are no tests to run)
Packaging up a single compiled artefact for production
Building ClojureScript applications with Figwheel generates lots of files under target/public
, as this is the most efficient way to push changes to the JavaScript engine application during development. Using only a single file when deploying your application to the live system makes your application website faster to load (only one http request).
The ClojureScript compiler has four :optimizations modes :none, :whitespace, :simple and :advanced.
The figwheel-main
template provides a :min
alias to generate a single minified file that has been run through the Google Closure compiler to eliminate any uncalled code. This generates a single file called target/public/cljs-out/dev-main.js
Publish the application by manually copying the file to a suitable deployment directory (or write a script to do so) when you publish your application live.
Deploying to GitHub pages
GitHub pages and GitLab pages provide fast and free service for running an HTML website, serving HTML, CSS and JavaScript files.
By placing all the web pages and asset files in a docs
directory, these services can be configured to serve those assets publicly.
Create a new build configuration to output the single JavaScript file to the docs
directory, typically in a js
sub-folder or any preferred directory structure.
Create a file called pages.cljs.edn
to represent a new build and add the following configuration
{:main practicalli.hello-world
:output-to "docs/js/hello-world.js"}
Edit the project deps.edn
file and add a new alias to deploy to GitHub/GitLab pages directory
:pages {:main-opts ["-m" "figwheel.main" "-O" "advanced" "-bo" "pages"]}
Create the deployable JavaScript file using the following command:
clojure -M:fig:pages
Copy the /resources/public/index.html
and any other web assets to the /docs
directory and update the /docs/index.html
to refer to the correct location of the JavaScript file generated by Figwheel.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/style.css" rel="stylesheet" type="text/css">
<link rel="icon" href="https://clojurescript.org/images/cljs-logo-icon-32.png">
</head>
<body>
<div id="app">
</div> <!-- end of app div -->
<script src="js/hello-world.js" type="text/javascript"></script>
</body>
</html>
See package a single file for production for more details.
Summary
Using Figwheel provides an simple way to develop, test and deploy ClojureScript projects, providing instant feedback as you develop to see exactly what the code does and help minimise bugs and avoid inappropriate design choices.
lambdaisland/kaocha-cljs enables using kaocha test runner with ClojureScript project, although I am still working on an example once I've resolved an issue with the configuration
Add aliases and build configurations customise the workflows for greater flexibility. The configuration files are EDN, so are Clojure maps that are simple to work with and understand.
There are more examples of options for figwheel-main projects on the https://figwheel.org/ website.
Please see earlier articles in the Clojure CLI series for background:
- Clojure CLI and tools.deps
- A Deeper Understanding of Clojure CLI Tools
- Gaining confidence with Clojure CLI tools
- Development workflow with Clojure CLI tools
- Community Projects for Clojure CLI tools
Thank you. Practicalli website | @practical_li