SkillAgentSearch skills...

Revolt

Your trampoline to Clojure dev toolbox

Install / Use

/learn @mbuczko/Revolt
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Clojars Project

Introduction

TL;DR: revolt is a plugins/tasks oriented library which makes it easier to integrate beloved dev tools like nrepl, rebel readline or clojurescript into application, based on Cognitect's command line tools.

To see it in action look at revolt-edge example.

Clojure projects historically leverage the power of two glorious build tools: leiningen and boot. Both battle-tested, feature rich alternatives allow to choose either declarative (leiningen) or fully programmable way to manage with tons of dependencies, dev / prod builds and whole bunch of tasks crucial for clojure developer.

The choice was relatively easy. Up util recent.

One day, according to their tradition, Cognitect surprisingly announced a new player on the stage - a command line tools for running Clojure programs (Deps and CLI Guide). It was not only about a new tool, the most significant improvement presented to community was entirely new way of working with dependencies. Things like multi-module project with separate dependencies and dependencies stright from git repo became possible just right out of the box.

Despite of this awesomeness people started wondering how to join all these toys together. Drop well known lein/boot and go along with Cognitect's way or to use new deps for application-specific dependencies and still leverage boot/lein tools for development only? Or maybe ignore new kid on the block and stick with bullet-proof tools we used for ages?

One of most interesting moves within this area was JUXT's edge and their attempt to build a simple but complete Clojure project based on the newest and most experimental things found around. Yeah, including Cognitect's new dependencies.

Revolt is inspired by JUXT's edge and, at its core, tries to simplify attaching all these shiny tools to the project by gathering them in form of tasks and plugins. And yes, it depends on Cognitect's dependencies underneath and makes heavy use of newly introduced aliases by the way.

Doesn't it sound like a Lisp Curse again? :)

What's in the box?

A couple of plugins you may really like:

  • [x] Rebel REPL to give you best REPL experience
  • [x] Figwheel must-have for web development
  • [x] nREPL obviously to let you use Emacs and Cider
  • [x] Filesystem watcher able to watch and react on files changes

and a few built-in tasks:

  • [x] scss - transforms scss files into css
  • [x] cljs - a cljs compiler
  • [x] aot - ahead-of-time compilation
  • [x] jar - jar packager
  • [x] test - clojure.test runner based on Metosin's bat-test
  • [x] info - project info (name, description, package, version, git branch, sha...)
  • [x] assets - static assets fingerprinting
  • [x] capsule - capsule packaging
  • [x] codox - API documentation with codox

External tasks planned:

  • [x] migrations - flyway based database migrations
  • [x] catapulte - jar files installer/deployer
  • [ ] lint - linter based on eastwood
  • [ ] analyse - static code analyzer based on kibit
  • [ ] ancient - looking for outdated dependencies

Plugins

Plugins are these guys who always cause problems. No matter if that's boot or lein, they just barely fit into architecture with what they do. And they do a lot of weird things, eg. nREPL is a socket server waiting for connection, REPL is a command line waiting for input, watcher on the other hand is a never ending loop watching for file changes. Apples and oranges put together into same basket.

Revolt does not try to unify them and pretend they're same tasks as cljs compilation or scss transformation. They are simply a piece of code which starts when asked and stops working when JVM shuts down. Nothing more than that. Technically, plugins (as well as tasks) are identified by qualified keyword and configured in a separate file. Typical configuration looks like following:

{:revolt.plugin/nrepl    {:port 5600}
 :revolt.plugin/rebel    {:init-ns "foo.system"}
 :revolt.plugin/figwheel {:builds ["main"]}
 :revolt.plugin/watch    {:excluded-paths ["src/clj"]
                          :on-change {:revolt.task/sass "glob:assets/styles/*.scss"}}}

Right after activation plugins usually stay in a separate thread until deactivation phase hits them in a back which happens on JVM shutdown, triggered for example when plugin running in a main thread (like rebel) gets interrupted.

Plugins love to delegate their job down to someone else, as all those bad guys do. In our cruel world these are tasks who handle most of ungrateful work on behalf of Master Plugins. As an example: watch plugin observes changes in a filesytem and calls a sass task when *.scss file is altered. Sometimes, task has to be explicitly configured to have plugin working, as it takes place for example in figwheel case which needs cljs task configured to run.

Ok, but how to specify which plugins do we want to activate? This is where clj tool from Cognitect comes onto scene, but more on that a bit later...

Tasks

If we called plugins as "bad guys", tasks are definitely the opposite - kind of little dwarfs who are specialized to do one job and do it well. And similar to plugins, there is a bunch of built-in tasks ready to serve you and take care of building and packaging your application. Oh, and they can generate documentation too.

To understand how tasks work, imagine them as a chain of dwarfs, each of them doing specific job and passing result to the next one:

clean ⇒ info ⇒ sass ⇒ cljs ⇒ capsule

which can expressed as a composition:

(capsule (cljs (sass (info (clean)))))

or in a bit more clojurey way:

(def build (comp capsule cljs sass info clean))

This way calling a build composition will clean a target directory, generate project information (name, package, version, git sha...), generate CSSes and finally pack everything into an uberjar (a capsule actually). Each of these tasks may generate intermediate result and pass it as a map to the next one in a context, eg. info task gathers project related information which is at the end passed to capsule which in turn makes use of these bits to generate a correct package.

To have even more fun, each task can be pre-configured in a very similar way as plugins are:

:revolt.task/info  {:name "foo"
                    :package bar.bazz
                    :version "0.0.1"
                    :description "My awesome project"}

:revolt.task/test  {:report :pretty}

:revolt.task/sass  {:source-path "assets/styles"
                    :output-path "styles"}

:revolt.task/codox {:source-paths ["src/clj"]
                    :source-uri "http://github.com/fuser/foo/blob/{version}/{filepath}#L{line}"
                    :namespaces [foo.main foo.core]}

:revolt.task/cljs  {:compiler {:optimizations :none
                               :output-dir "scripts/out"
                               :asset-path "/scripts/core"
                               :preloads [devtools.preload]}
                    :builds [{:id "main-ui"
                              :source-paths ["src/cljs"]
                              :compiler {:main "foo.main"
                                         :output-to "scripts/main.js"}}]}

:revolt.task/assets {:assets-paths ["assets"]}

:revolt.task/capsule {:exclude-paths #{"test" "src/cljs"}
                      :output-jar "dist/foo.jar"
                      :capsule-type :fat
                      :main "foo.main"
                      :min-java-version "1.8.0"
                      :jvm-args "-server"
                      :caplets {"MavenCapsule" [["Repositories" "central clojars(https://repo.clojars.org/)"]
                                                ["Allow-Snapshots" "true"]]}}

Let's talk about task arguments now.

Having tasks configured doesn't mean they are sealed and can't be extended in current REPL session any more. Let's look at the sass task as an example. Although it generates CSSes based on configured :source-path, as all other tasks this one also accepts an argument which can be one of following types:

  • A keyword. This type of arguments is automatically handled by revolt. As for now only :help responds - returns a human readable description of given task.
  • A java.nio.file.Path. This type of arguments is also automatically handled by revolt and is considered as a notification that particular file has been changed and task should react upon. sass task uses path to filter already configured :resources and rebuilds only a subset of SCSSes (if possible).
  • A map. Here it's up to task how to handle this kind of argument, by convension revolt simply merges provided map into existing configuration:
(info {:environment :testing})
⇒ {:name "foo", :package "bar.bazz", :version "0.0.1", :description "My awesome project", :environment :testing}

Sometimes, in particular when tasks are composed together, it may be useful to provide argument with help of partial function:

(def build (comp capsule cljs sass (partial info {:environment :testing}) clean))

This way we can tinker with our builds and packaging in a REPL without changing a single line of base config

Related Skills

View on GitHub
GitHub Stars52
CategoryDevelopment
Updated10mo ago
Forks4

Languages

Clojure

Security Score

77/100

Audited on May 26, 2025

No findings