A trick to auto wire Elm pages

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 :wink: 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.

23 Likes

@akoppela Thank you very very much for reporting your experience in this topic!

I am working on a few applications that are growing in size, and I keep asking myself how can I deal with the boilerplate more intelligently. For a while I desired a “TemplateHaskell”-similar feature (or some kind of macro system), but I know that such a system can be abused very easily. I would love to hear the thoughts of the community on this topic though :slight_smile:

So, I ended up writing all the boilerplate by hand, while sketching a few scripts to do that for me. I’ll for sure check the CodeGen package you mentioned!

1 Like

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