Four years ago, I published the first version of Outboard, my iOS app for saving and organizing places, written in ClojureScript on React Native. While the experience of building a functional, reactive UI in ClojureScript was a dream, the experience of duct-taping the various fast-moving bits of Javascript build pipelines and compilation systems was not. Over the course of the four years since then, React Native has been through ten “minor” versions, each with major breaking changes, making upgrading to the current version a nearly impossible task. By contrast, all of my ClojureScript code worked perfectly on new ClojureScript releases, so I decided to simply lift the app code out, build a new React Native base underneath it at the current version, and put it back on top.

This worked surprisingly well!

The New Wave

One upgrade I had made to the 1.x version of Outboard was moving it from its original base on the (now defunct) re-natal to the sparer, lighter Krell. Krell does the minimum needed to prepare ClojureScript’s Google Closure-compiled Javascript for the Metro bundler system used by React Native, and consequently incorporates NPM dependencies and local JS less jankily.

These improvements then make the Vouch.io workflow demonstrated in this talk by David Nolen possible: Build dumb components in pure Javascript and JSX, test and design them in Storybook, and then consume and present them from your ClojureScript app, which uses re-frame to maintain app state. This comes with a number of benefits, leaning on each library for what it does best and discarding its weaknesses:

  • The app is coordinated in ClojureScript, with its immutable data structures eliminating whole classes of bugs
  • State is the responsibility of re-frame, which has a simple and boilerplate-free functional interface
  • UI components use JSX as markup, and consumption of Javascript/Typescript UI libraries is seamless
  • Pure JS/JSX components reduce the temptation to spread state references up and down the stack
  • Storybook uses React Native Web to present those JS components locally for design tweaks

Once these pieces are fit together, the development experience is blissful. The functional ClojureScript shell I had built for version 1 worked without changes, and new features I wanted to add required no refactoring. With JSX and Storybook, I was able to quickly and easily incorporate Tailwind for React Native to first replicate the original design, then significantly improve it. Design changes propagate to the app running in the simulator or on a phone instantly, with the ClojureScript REPL connected to test how it reacts to state changes. The frustrating tweak-compile-click-check-tweak cycle is gone.

Setup

Finding up-to-date (or any) documentation on setting this all up was a frustrating experience, but I’ve hopefully solved a lot of the pain with a new React Native template that does it all for you.

Starting a new project with Krell, Storybook, and Javascript components is now a one-liner:

npx react-native init YourProjectName --template react-native-template-cljs-krell-storybook

In Action

The complete workflow of both designing your components with Storybook and running a ClojureScript REPL boils down to three processes:

yarn cljs:repl

This runs the Krell REPL via Clojure’s CLI (clj -M -m krell.main -co build.edn -c -r).

yarn run ios # or android

This compiles and runs the app on the simulator, as well as starts up the Metro bundler.

yarn storybook

This starts the Storybook server and opens it in your browser, and will refresh as you make changes.

Conclusion

As in my original 2018 blog post, there continue to be tradeoffs with chaining together a number of different languages and platforms. Combining the benefits of functional, immutable development in ClojureScript with the ubiquity and gigantic ecosystem of React Native can make for an astoundingly productive flow, but that flow can be interrupted by mismatches between the various toolchains and build systems you have to connect to make it work. For me, though, as long as ClojureScript is an available platform to build apps like this on, I will continue to make that tradeoff.