Hi. I’d like to share a cool trick (maybe) we use at Conta AS to auto wire Elm pages. The article can be useful for companies with a lot of pages as well as a learning resource of slightly alternative architecture for Elm programs.
Let’s define what Elm page is.
An Elm page is basically an Elm program with it’s Model
, update
, view
, subscriptions
, etc. Such a page is typically connected in the Main
module. For each page in Main
module there is a Model
constructor which wraps page model, there is a Msg
constructor which wraps the page msg, the same for init
, update
, view
and so on.
Let’s see what router is
Additionally each page is connected to some URL. Canonical example teaches us to create a Route
module with it’s Route
type. For each Route
constructor there is a URL parser and a function to convert it back to URL string and so on. Sometimes more than one route can represent a page. E.g. new and edit forms, URLs are different but page content is the same.
How does page initialization happen
Typically in Main
module in init
function there will be a call to parse current URL. After parsing we get a Route
constructor. Then we have to manually match the constructor with the page and so on.
How update function works in Main module
Typically update
pattern matches on both Msg
and Model
constructors (maybe via tuple pattern match). Most of the times it’s not exhaustive pattern match and it ends up with wildcards. So compiler can not help there anymore. So it can be a source of potential troubles after connecting a page but not the update function. Elm compilers is good so you may expect that everything should work if compiler says so =)
Let’s see what the process of adding a new page is.
Typically when adding a new page a person would create a new Elm module for the page and wire it up manually. Create a new Model
and Msg
constructors, wire update
, view
, connect init
function and so on.
Can we do better?
As we can see it’s may be a very time consuming process. But most of the times it’s the same process over and over again. We can automate it by generating a module which does all the wiring for us.
How? Let’s find all the pages first.
So the idea is to auto generate a module which does the wiring for us. First of all we have to identify which module is a page module. Because there can be other modules, e.g. with reusable UI elements, etc… This is totally a matter of taste. Many people group files by types. E.g. Pages
, Api
, Utils
, etc… In this case it’s very easy to find all the pages, because they all will be in Pages
folder. At Conta AS we like to group files by feature. The reasoning is simple, all files for particular feature will be located in one place, which can be easier to find and work with (again it’s a matter of taste). So in this case maybe we can mark a page with some identifier, let’s say a module with a page will always end with ...Page.elm
. We can then recursively search the source folder and find all the pages.
The generation process
After we found all the pages we can just generate an Elm module with all the wiring. The Elm module will contain Model
, Msg
, init
, update
, etc… Everything what is needed to wire a page. Again here is a matter of taste what scripting tool to use. Maybe Elm, maybe Node, maybe Haskell, maybe Go, whatever. There is a cool Elm package to create Elm modules with Elm. We use it internally.
Are we done yet?
Wait, what about router? It’s straight forward how to generate everything because it’s mapped one to one. But router does not map one to one with pages. How do we solve it? Well it depends on your taste again Maybe you’d like to use a directory structure as a map of your router. Then it’s easy to connect each page because it will have one to one mapping. There will be a question on how to reuse page. Basically there will be a module for each URL which then internally will reuse some of the logic. Alternatively we can specify what URLs belongs to what page with source code. E.g. we can create a urlParser
function per page. The function will describe which URLs belongs to the page with Url.Parser
module. For each page we can have a UrlParams
data structure which will represent parsed URL params which will then be passed to init
function. And so on! I really like the latter because the page will describe everything it needs in one place and multiple URLs can be used for one page without additional modules.
Conclusion
It’s definitely possible to automate the process of wiring the page. It’s just a matter of finding a pattern and trade offs. Maybe you want to rely on folder structure more, or maybe you want to rely on naming. In the end it’s how you can generalize stuff, right?
Our use case
At Conta AS we have around 150 pages. You can imagine how long the Main
module would be and how frustrating it can be to find the correct place. It works really well for us to generate a module which auto wires all the pages. Then in Main
module we simply wire the auto generated module once and for all. Additionally we have a CLI tool to bootstrap a page. Which under the hood just copies the template to the right place. When new page is created there is a watcher to re-generate the module for page wiring. After page wiring module is generated the whole project is recompiled again. So it does really speed up the development.
Other examples
I believe elm-spa
does it in very similar way. There is a CLI tool to generate pages, it auto generates router and wiring for you. We have created our solution before elm-spa
was born. But it’s very nice to see that a similar approach works for someone else. Well done.
Thank you
Yes, thank you for reading through. Please feel free to ask questions,
Andrey.