Some introductory text
- Overview
- Language/library references
- Blog posts etc.
- Component / style resources
- Build cheatsheet
- Repl cheatsheet
- Documenting code
- Stack components
A my-first-cljs-project implementing the following completely unrelated views/functions:
- A webserial terminal
- A todo app (adapted from the uix-starter example)
- A quick-and-dirty html -> uix syntax converter for integrating html samples grabbed from tailwind site etc.
This is an exploratory effort kicking the tyres of the following technologies:
- UIx2
- UI toolkit / wrapper for React
- shadow-cljs
- build system
- refx
- UI data store (re-frame using react hooks)
- clj-statecharts
- UI/subsystem state management (integrated with refx)
- Portfolio
- component-driven development (similar to devcards / storybook.js)
- Metosin malli
- data schema - selective typing / design-by-contract and generative testing.
- glogi
- logging framework
- TailwindCSS
- utility CSS classes (as a PostCSS plugin)
- DaisyUI
- pre-built Tailwind components (for instant gratification - but I’m incrementally phasing this out)
- svg icons
- Extracted from the phosphor and/or fontawesome font sets (using two excellent libraries written by Christian Johansen)
- webserial API
- serial port access from the browser
Started with the uix-starter project from pitch.io (= UIx2 + shadow-cljs), then added the other bits and pieces.
The webserial terminal currently has an optional filter for FTDI devices I’m mostly using when selecting a port.
The included TODO MVC example / widget started out as a synthesis of the excellent examples provided with the UIx2 starter kit and refx - the refx end of things has been simplified / reworked quite a bit in the interest of composability and component-driven development using portfolio.
The yarn config in ./package.json allows the app, test and portfolio builds to be run individually or in parallel.
yarn all
will run the app, test and portfolio builds on ports 8080, 8081 and 8082, respectively.
There’s plenty here that’s only (if even) partly thought through, and likely downright unidiomatic, but it’s been useful for me to get a feel for cljs development.
Deployed as a static site using github pages and gitlab pages.
Serial terminal functionality working in supported browsers (i.e. chrome, chromium, edge) for both.
N.B. use of webserial requires a secure context (i.e. running locally, or connected remotely via https). This had me puzzled for a while when trying to deploy on a self-hosted gitlab instance without proper certs setup.
Github pages deployment done by the ugly/expedient method of checking in built (unminified) js and css. Available at https://cormacc.github.io/cljserial/
The gitlab pages deployment is a bit cleverer, in that uses a build pipeline rather than checked-in build artefacts. Available at https://cljserial-cormacc1-0e5f9b68c5dce51c18dfd8ac9fec3118aa93742f32dd.gitlab.io/
N.B. I’ve made token efforts at caching dependency folders etc. for the gitlab CI build, but ineffective so far. To be revisited.... See ./.gitlab-ci.yml
Pretty console logging via glogi requires custom formatters to have been enabled for your browser dev tools window – see: https://github.com/binaryage/cljs-devtools/blob/master/docs/installation.md
Without this config, the log entries will contain lots of less useful js data structure.
Top level:
- entrypoint
- ./src/cljserial/core.cljs
Rather than centralising the data model and refx setup for unrelated functions, functionality has been composed in vertical (hopefully) independent slices.
Controller / hardware interface:
- ./src/cljserial/services/webserial.cljs
- The serial port controller – a clj-statechart state machine integrated with refx
- ./src/cljserial/utils/webserial.cljs
- Cljs interface wrapping the webserial api
UI
- ./src/cljserial/pages/terminal.cljs
- The terminal page / view – glues together component and refx database
- ./src/cljserial/components/term.cljs
- The UI component.
- ./portfolio/src/cljserial/components/term_scenes.cljs
- The portfolio scenes for the UI widget
- ./src/cljserial/pages/todo_mvc.cljs
- The Todo app page / view – glues together component and refx database
- ./src/cljserial/components/todo.cljs
- ./src/cljserial/services/todo.cljs
- ./portfolio/src/cljserial/components/todo_scenes.cljs
./src/cljserial/pages/converter.cljs
- cljs-react-devtools
- Debug utilities by Roman Liutikov (UIx author)
- supabase
- Use this rather than aws amplify for backend?
- Datascript
- integrate with refx – this needed re-posh when using re-frame, but refx docs suggest that integration with datascript has been considered there already....
- UI string localisation (using ?)
- maybe pottery?
- automated unit test browser interface using kaocha? Though the shadow-cljs test runner looks fine so far…
- Headless UI (component kit from Tailwind Labs)?
- squint cljs -> js compiler
- https://blog.michielborkent.nl/porting-cljs-project-to-squint.html
- js-interop library
- js interop not a pain point so far, but keep it in mind…
- flow-storm debugger
- datapotato
- Schema-based test data generation
- preo
- Pre and post hooks for function args and return values..
- electric
- For frontend/backend sync? Looks very interesting, but a quite different approach (i.e. not something to integrate with the rest of this stack).
- re-frame
- Using refx currently as that seemed to be more appropriate with uix / modern react (based on some naive/shallow reading a few months back), however re-frame is the reference / more actively developed and has useful development aids (e.g. re-frame-10x, re-frame-flow), so likely worth taking a look at. It also works with uix, apparently.
- Clojure spec
- selective strict typing / design-by-contract and for generative testing. Used this initially, but moved to malli - primarily because syntax easier to parse visually (matches data structure).
This project includes some (now disabled – see below) integration with AWS via amplify.
Initially used the @aws-amplify/ui-react
package to provide their auth component, which is pretty bloated – including just the auth component makes the build jump from 275 files to 1243 files.
In fact I think I’ll look at supabase before spending more time on this - not loving the AWS amplify experience to date.
The required AWS libs are still referenced in ./package.json for now… To re-enable, just uncomment the relevant lines in ./src/cljserial/core.cljs
- [X] Amplify (meta/configuration)
- [X] Cognito (auth)
- [ ] S3 (file storage)
- Pre-signed uploads to AWS S3 using Clojure(script)
- Excellent clear (and detailed) blog post from 2022 on uploads to AWS S3, using re-frame coeffects etc.
For instructions on setting up your amplify environment, see here: https://docs.amplify.aws/react/tools/cli/start/set-up-cli/
This is required to generate the aws-exports.js needed for the build.
A couple of the aws dependencies have some post-installation steps – a script has been provided to automate this: ./patch-aws-dependencies.sh
This should be run…
- After running
yarn install
. - After updating the
@aws-amplify/ui-react
package.
(Don’t judge me, I’m a C programmer)
- re-frame docs
- Excellent reference for re-fx, and general overview of state management in front-end development
- Clojure style guide
- Wot it says on the tin....
Other peoples experience of working with clojure/clojurescript stacks…
- https://whimsical.com/blog/how-we-built-whimsical
- clojure-based shop doing visual collaboration software. New home of Roman Liutikov (UIX author)
- html->hiccup converter
- TODO - fork this for uix syntax? See https://github.com/buttercloud/html2hiccup
The majority of these are handled by via:
In addition, fontawesome is required at build time to unpack svg for icons. This can be downloaded into your source tree by executing the snippet below (from https://github.com/cjohansen/fontawesome-clj):
clojure -Sdeps "{:deps {no.cjohansen/fontawesome-clj {:mvn/version \"2023.10.26\"} \
clj-http/clj-http {:mvn/version \"3.12.3\"} \
hickory/hickory {:mvn/version \"0.7.1\"}}}" \
-M -m fontawesome.import :download resources 6.4.2
- uix-starter for initial UIx2/shadow-cljs setup
yarn install # install NPM deps
yarn dev # run dev build in watch mode with CLJS REPL
A shadow-cljs job has been setup to watch the test/src tree for changes and display results in the browser. Per https://shadow-cljs.github.io/docs/UsersGuide.html#_testing.
yarn test
Open http://localhost:8081 for test results.
N.B. Currently we need to run either portfolio or the app – there’s probably a way to do both…
yarn portfolio
yarn release # build production bundle
yarn all
shadow-cljs includes a browser based interface allowing the runtime (function defs and data) to be inspected and modified – effectively a GUI for the REPL. Accessible at http://localhost:9630
See https://shadow-cljs.github.io/docs/UsersGuide.html#_clojurescript_repl
To open a repl session from a shell prompt
npx shadow-cljs cljs-repl app
Currently using Emacs/Cider. The docs provided for cider are excellent. Read them.
- Clojure doc strings support markdown syntax (i.e. can include tables and code examples)
- Refer to function arguments using backticks.
- Refer to other functions using wikilink syntax ‘[[]]’.
See example below
(defn do-something [] ... )
(defn do-something-else
"Do something `n-times` times. See also [[do-something]]."
[n-times]
....)
See short but sweet blog post by Martin Klepsch here.
Using refx (re-frame without reagent) as a centralised app state store.
- re-frame on github / docs
- Overview of concepts etc.
- https://github.com/ferdinand-beyer/refx
- Includes some brief if excellent documentation / examples provided by the author.
- re-frame patterns for composability
- Nice post on patterns for parameterising events and subscriptions (rather than hard-coding app-db paths and event IDs etc.) for composability.
Also using clojure.spec to define a schema for application state – validated per database update using refx interceptors (based on refx example). This approach may be heavyweight in practice, but a nice demonstration of the capabilities.
The intent is to model high-level application state using one or more statemachines. See ./src/cljserial/hsm_refx.cljs for some code integrating refx and cljs-statecharts. See ./src/cljserial/state/serial_states.cljs for a usage example.
- clojure.spec - Rationale and Overview
- Official docs.
- How do you use clojure.spec
- By Sean Corfield - very knowledgeable clojure contributor.
- JUXT / Generative UI testing with clojure.spec
- Useful very short post
Sounds promising – see here:
- https://francisvitullo.medium.com/a-way-of-testing-views-in-clojurescript-apps-98aaf57c5c2a
- https://gist.github.com/cdbkr/f195d7fbb600fae9655f37e7b2b4813e
This guy does something similar, but bypassing use of react-testing-library:
- TailwindCSS and PostCSS integrated following instructions here
If portfolio is injecting itself into the app index.html (which references the generated tailwind.css directly), the styles make the Portfolio navigation panel unreadable (Portfolio usually injects the styles into the iframe displaying your scenes rather than the root – I think).
To workaround this, the portfolio dev server uses a different index.html, but the app “public/css” folder is included in the dev server paths. This ensures we’re picking up production css without having to duplicate the css watch task. It just means that we reference “css/tailwind.css” from the app’s ./public/index.html and “/tailwind.css” from ./portfolio/resources/public/index.html.
Watch this space. Some of the tooling uses keys / cljs dictionaries – would rather something gettext / po file based.
https://github.com/puppetlabs/clj-i18n
This mentions clojure and lein only - not immediately clear to me whether viable for clojurescript project.
https://github.com/taoensso/tempura
This prefers using keys at the point of use rather than just using the string itself as the key (the GNU gettext / .po-file tooling I’m used to from python work). It does allow the use of plain (fallback) strings during development, but requires insertion of keys for lookup later – which seems laborious / inelegant.