Skip to content

cormacc/cljserial

Repository files navigation

cljserial

Some introductory text

Table of Contents

Overview

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.

Deployment to github/gitlab pages

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

Github pages deployment done by the ugly/expedient method of checking in built (unminified) js and css. Available at https://cormacc.github.io/cljserial/

gitlab pages

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

Browser console logging

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.

Source code organisation

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.

WebSerial terminal

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

ToDo app

./src/cljserial/pages/todo_mvc.cljs
The Todo app page / view – glues together component and refx database

HTML -> UIx converter

./src/cljserial/pages/converter.cljs

To add/investigate

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 ?)
  • 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…
datapotato
Schema-based test data generation
preo
Pre and post hooks for function args and return values..

Interesting alternatives

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.

Investigated / not adopted

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).

AWS integration

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

Services to integrate

  • [X] Amplify (meta/configuration)
  • [X] Cognito (auth)
  • [ ] S3 (file storage)

References

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.

AWS local environment setup

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.

AWS dependency patching / post-installation steps

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…

  1. After running yarn install.
  2. After updating the @aws-amplify/ui-react package.

Language/library references

(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....

Blog posts etc.

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)

Component / style resources

html->hiccup converter
TODO - fork this for uix syntax? See https://github.com/buttercloud/html2hiccup

Build cheatsheet

Dependencies

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

Source materials

Initial setup

yarn install # install NPM deps
yarn dev # run dev build in watch mode with CLJS REPL

Test build

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.

Component-driven development (portfolio) build

N.B. Currently we need to run either portfolio or the app – there’s probably a way to do both…

yarn portfolio

Production build

yarn release # build production bundle

Combined development build (dev app, unit test runner and portfolio)

yarn all

shadow-cljs inspect

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

Repl cheatsheet

See https://shadow-cljs.github.io/docs/UsersGuide.html#_clojurescript_repl

Open a repl session from a shell

To open a repl session from a shell prompt

npx shadow-cljs cljs-repl app

Open a repl session from your editor

Currently using Emacs/Cider. The docs provided for cider are excellent. Read them.

Documenting code

  • 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.

Stack components

State management

Application data storage using refx (re-frame)

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.

Hierarchical state machines using cljs-statecharts

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.

Testing

Generative testing using Spec and Spec test

Links

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

Candidate approaches

cljs.test and react-testing-library

Sounds promising – see here:

This guy does something similar, but bypassing use of react-testing-library:

CSS

  • TailwindCSS and PostCSS integrated following instructions here

DaisyUI / Portfolio CSS incompatibility

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.

Internationalisation

Watch this space. Some of the tooling uses keys / cljs dictionaries – would rather something gettext / po file based.

clj-18n

https://github.com/puppetlabs/clj-i18n

This mentions clojure and lein only - not immediately clear to me whether viable for clojurescript project.

tempura

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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published