[ANN] cljs-spa-example: a demo SPA based on Figwheel Main

cljs-spa-example is an opinionated demo project. The repository contains a simple Single Page Application (SPA) based on the new Figwheel Main build tool. It demonstrates what I consider best practices for building ClojureScript web applications today, covering build tooling, navigation, library choice and reloading.

The repository can also be used as a template to provide a solid foundation for projects using Reagent and Figwheel Main - all you need to do is to clone the repo and search/replace the project name.

Feedback welcome, here or as issues on Github!

12 Likes

This is neat.

I did a quick port to shadow-cljs for those interested in seeing the differences configuration wise. I only made a few adjustments to the npm requires since shadow-cljs uses npm directly instead of going through the doublebundle. The names exported by that didn’t quite match up. Otherwise the code remains the same and only the configs change.

10 Likes

Nice, thanks for taking the time!

There’s a working PR for cljs-spa-example in which I add support for Figwheel Main’s new npm option (not to be confused with npm-deps). npm autogenerates the foreign-libs compiler option by parsing library.js, removing a lot of duplication. The PR requires a snapshhot version of Figwheel Main so I’m holding off on merging, but it makes the configuration a lot nicer.

Having said that, the configuration in your shadow-cljs branch does look pretty clean. I like the "react-select" :default react-select trick - is that a shadow-cljs extension?

I also like that shadow-cljs makes advanced compilation work out of the box. (In the branch I had to remove the leading slash in index.html like so: <script src="js/main.js" type="text/javascript"></script>)

For comparison, I just merged my advanced compilation branch: to make it work I had to enable infer-externs and add type annotations manually.

Thanks for the example app, @pesterhazy ! And thank you for the Shadow CLJS version, @thheller . The Shadow CLJS version is very clean indeed.

I made a few changes here to work with re-frame.

Yes, it is a proposed extension currently only available in shadow-cljs. I did a few enhancements to the externs inference but it is mostly aided by the JS processing. Since shadow-cljs processes all of the JS it generates a bit more externs for the exports it finds. That is usually sufficient but type hinting is still required sometimes and works equally well.

Today I added bhauman’s new cljs-test-display to the repo to show how to do browser tests

Looks good. Router5 looks solid and seems to have a lot of nice features. Have you checked any of the ClojureScript routers out there? What is missing? We are developing reitit, which allows similar routing state model as in the example for the frontend, example here.

1 Like

@ikitommi, to elaborate, here’s how I see the job of a client-side router. Routing is composed of 3 parts:

  1. a simple path parser ("/home" -> {:route :home, :params {}})
  2. a function that hooks up your code to the HTML5 History API (or sets up a onhashchange listener)
  3. a module that listens for changes and in response causes side-effects (like network requests) and changes to app state (like setting :route in app-db to :home)

Traditional CLJS libraries cover 1 and 2 well (my favorites are bidi and accountant, respectively) but AFAIK don’t really address 3. router5 combines 1-2 but also provides the missing link, 3.

The main idea is that the router is a stateful object that publishes messages, which in turn a simple switcher component in the view layer can subscribe to. Additionally Router5 provides a framework for registering page lifecycle hooks (on-activate and on-deactivate), and it has the notion of a navigation action moving from a certain page to another, so you can e.g. do transitions differently depending on where you’re coming from.

Thomas Roch, the author of Router5, explains the design and advantages over react-router in this conference talk.

That said, I haven’t looked at retit closely (it looks great!). Looking at the docs, it supports controllers, which is quite similar to what I had in mind. I’d be curious if you think the above is a good description of the problem to be solved by a router.

1 Like

thanks, a great talk, and agree that 3 is important too.

Last time I was using a pure js-router I think it was the ui-router, which also does the 3 and has a really fine-grained support for state and state transitions.

With ClojureScript, there is Keechma doing 3 too (a framework). Kee-frame (for re-frame) and reitit (just routing) are both crediting to Keechma for the statfull routing with controllers. In reitit, the controllers are small optional add-on on top of the frontend router. Good to have options, I guess.

2 Likes

thank looking for re-frame version and here it is.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.