Designing UI components with Clojurescript, Replicant and Portfolio
Portfolio showing components of the application view, and the actual application, side by side.
Introduction
In my previous blog post Frontend Development with Clojurescript and Replicant I wrote about implementing a UI using the excellent Replicant library. In this new blog post I tell my experiences on how to use another Christian Johansen’s great library: Portfolio.
The exercise I explain in this blog post is in my Clojure Github repo in directory replicant-webstore.
My Current Frontend Stack
But let’s first introduce my current favorite frontend tools:
- Clojurescript. A functional language with immutable data structures that transpiles to Javascript (like e.g. Typescript does).
- Tailwind CSS. An easy way to create various CSS stuff. Integrates well with hiccup and Clojurescript.
- Hiccup. Hiccup provides a very succint and functional way to express HTML that transpiles to HTML code.
- Replicant. An excellent rendering library with central event handling and store.
- Gadget. Gadget provides a great view to your centralized store - the state of your store is basically your UI.
- Portfolio. Using Portfolio you can create a catalog of your UI component straight from your production view code.
All these tools work amazingly well together. Using Clojure(script) is such a joy to work with. You can use your favorite programming language both in the backend and frontend sides, and share code (like schemas) between the backend and the frontend. And the Clojure (Lisp) super tool - the REPL - is available both in the backend and in the frontend development workflow.
What is Portfolio?
The Portfolio repo says:
Portfolio brings some of the best features of Storybook.js to ClojureScript, and adds a few of its own. While Storybook.js was its starting point, Portfolio does not aspire to feature-parity with it, and instead caters to the REPL-oriented ClojureScript development process.
The way I use Portfolio, is to setup a dummy replicant store and event handler for Portfolio, require the production view namespace in the portfolio namespace and tell Portfolio to show my UI components in the Portfolio view (see the picture at the beginning of this blog post).
I don’t explain Portfolio here any more than that. I encourage you to watch the Portfolio: A “visual REPL” for UI Component development (by Christian Johansen) - a presentation at the London Clojurians.
Show Me the Code
Let’s have a short example. I have various views in my frontend side. All these views are pure - they do not have any local state. The views only return hiccup based on the input arguments they receive. In this app the centralized application state will be supplied as the input argument. Whenever the state changes we tell Replicant to evaluate our views and render the resulting hiccup to the DOM.
An example of the views.cljc (note: this is a cljc
file - (c for “common”) - it can be used to render the view in the frontend Clojurescript side, and it can e.g. be tested in the backend Clojure (JVM) side - since there are no Javascript dependencies, and no local state):
(defn- show-error [msg button? dissoc-key]
[:div.inline-block.bg-red-50.border.border-red-500.rounded.px-4.py-3 {:role "alert" :style {:max-width "fit-content"}}
[:div.flex.items-center
[:p.font-bold.text-red-700 msg]
(when button?
[:button.text-xs.px-2.py-1.ml-4.rounded.bg-red-50.hover:bg-gray-300.cursor-pointer.border.border-gray-400
{:on {:click [[:db/dissoc dissoc-key]]}}
"X"])]])
(defn- show-info [msg button? dissoc-key]
[:div.inline-block.bg-blue-50.border.border-blue-500.rounded.px-4.py-3 {:role "alert" :style {:max-width "fit-content"}}
[:div.flex.items-center
[:p.font-bold.text-blue-700 msg]
(when button?
[:button.text-xs.px-2.py-1.ml-4.rounded.bg-blue-50.hover:bg-gray-300.cursor-pointer.border.border-gray-400
{:on {:click [[:db/dissoc dissoc-key]]}}
"X"])]])
Next, the Portfolio code in scenes.clj:
(ns frontend.scenes
(:require [portfolio.replicant :refer-macros [defscene]]
[portfolio.ui :as portfolio]
[frontend.views :as f-views]
[frontend.replicantutil :as f-rutil]))
(defscene show-info
(f-views/show-info "Hello info from Portfolio!" true :db/dummy))
(defscene show-error
(f-views/show-error "Hello error from Portfolio!" true :db/dummy))
(defn main []
(portfolio/start!
{:config
{:css-paths ["/css/main.css"]
:viewport/defaults
{:background/background-color "#fdeddd"}}}))
It’s as simple as that. And since using Replicant, you design your UI pure without local state, it is very convenient to develop the UI components in isolation using Portfolio.
Some auxiliary configuration.
Since my UI components use Replicant and expect an event handler (see: {:on {:click [[:db/dissoc dissoc-key]]}}
), you have to setup a dummy store and event handler. Not difficult. You can just copy-paste your production store and event handler and strip almost everything away. This is how I did it in replicantutil.cljs.
And then, of course, you need to setup the build and the routing for your Portfolio, shadow-cljs.edn:
:portfolio
{:target :browser
:modules {:main {:init-fn frontend.scenes/main}}
:dev {:output-dir "target/dev/public/portfolio"}}
… and routing routes.clj:
(defn portfolio-js-file []
(-> (io/file "target/dev/public/portfolio/manifest.edn")
slurp
edn/read-string
first
:output-name))
;; Open portfolio in http://localhost:9333/portfolio/index
(defn portfolio []
(hiccup/html {:mode :html}
(hiccup/raw "<!DOCTYPE html>\n")
[:html
{:lang "en"}
[:head
[:title "Portfolio Design Area"]
[:meta {:charset "utf-8"}]
[:link {:rel "icon" :href "/assets/favicon.ico" :type "image/x-icon"}]]
[:body
[:div#app]
[:script {:type "text/javascript" :src (str "/portfolio/" (portfolio-js-file))}]]]))
(defn app [env]
(ring/ring-handler
(ring/router
[""
["/portfolio"
["/index"
[""
{:get {:handler (fn [_req] (resp/ok (str (portfolio))))}}]]]
["/api"
;; ...
So, you can develop your UI components in isolation using Portfolio. Another great way of using Portfolio, is to create a portfolio, a catalog of your UI components, and the Portfolio view can be a playground for your UI designers and testers to examine e.g. the CSS styles, and internationalization of your components. Watch the Portfolio: A “visual REPL” for UI Component development (by Christian Johansen) presentation - it provides great examples on how Christian Johansen has been using Portfolio himself.
Conclusions
Portfolio and Gadget are two extraodinary tools to be used with Replicant. I encourage you to try Replicant, and Portfolio and Gadget with Replicant. It might change the way you think about building UI - at least it changed my way of thinking on how to build UIs - in a simpler way.
The writer is working at a major international IT corporation building cloud infrastructures and implementing applications on top of those infrastructures.
Kari Marttila
Kari Marttila’s Home Page in LinkedIn: https://www.linkedin.com/in/karimarttila/