In August 2015 I challenged myself (and later the rest of the Gizra devs) to create a typical web-app with all the bells and whistles in Elm. It’s called elm-hedley, and I’m super proud it is now featuring in Elm’s front page.
This post is going to give a high level overview and point out some parts that are worth noting. However, before diving into the technical section, it is important to emphasize the virtues of simply doing.
If you would go back in the commits history you would see some nasty stuff that have been completely overhauled and polished. The only way of getting to that “improving” part is of course by starting! Only after that will one become smarter and recognize what needs improving, as well as be more experienced to know how to do it.
MUV (Model-Update-View) Components
The Elm architecture is advocating for a Model-Update-View for each components.
Model holds the data; Update is in charge of changing the data based on actions; and View is responsible for showing the data or triggering an action on a certain event (e.g. onClick
).
We love this approach so much we have even ended up splitting each component to those files. So everything is consistent and makes finding stuff easy. The MUV is just a suggestion, you can add or remove stuff. Each component can also have a Test
, Router
, Utils
, or completely omit the Update
and View
if they are not needed.
Components Vs Pages
The way we structure the files is each component resides in its own folder. Pages which are just a container of other components are placed under the Pages
directory. In order to keep it simple, if a page is just a wrapper over a single component we won’t create both a component and a page. The page will be in fact a component. If one day the same page will need to become something more complex like the Event page, we can always move things around easily. The benefit of doing so is that it keeps things simple - no extra wiring needed merely for keeping things consistent (and harder to read).
Effects
In line with the “Keep it Simple” approach, we realized that it’s not required for every component to have an effect
. So we can just remove it. On the other hand if we need the component to return more info to the parent we can do that as-well. The Articles page is a good example: The ArticleForm component that is responsible for the form for creating articles doesn’t need to have the newly created article as part of its model - it just needs to create it, and it hands it over back to the parent component. The parent component will make sure to append it to the ArticleList.
Router
When building an SPA (Single Page Application) in Elm, this router package is fun to work with. If you are new to Elm’s syntax it might seem a bit daunting at first, but I can assure you it’s doing exactly what it should be doing - and not more. I’ve written in more detail about how we love the fact that the URL change is in fact just a side-effect.
Unit test and CI integration
I confess. In all our ~3 years of Angular we hardly did any unit testing. The setup and mocking was often too annoying, so we opted to do functional testing, which is of course great, but not as great as having both functional and unit tests.
It was super important for us to see that Travis badge telling us, go ahead, your latest PR didn’t break things.
While writing the tests was fun, as Elm’s pure functions make unit testing pretty much hassle free (see example), the integration with Travis-CI was more challenging. The tests were running fine on the browser, but in a node environment they started failing. Looking at existing examples we saw that we had to start mock the browser inside node for the tests to pass. Yeah, mocking - that thing we really tried to avoid.
Looking for a better solution we dusted off our CasperJS skills and came with a simple solution. Run the test via terminal using CasperJS (which in turn runs over PhantomJS). It worked!
We were so happy with the solution we’ve added it to our home grown Yo generator, so now everybody that uses it gets a fully working Travis integration.
Ports & Interop
Any non-trivial web app has to make use of existing libraries. Rewriting Leaflet for maps / CKeditor for WYSIWYG editor / Dropzone for file uploads in Elm doesn’t make much sense - it would be just too much work. So basically we let each JS library do its job and pass the information back to Elm that manages the state of the app.
After getting used to Elm’s compiler saving us from doing mistakes, developing JS “in the wild” suddenly feels scary :)
Admittedly some parts in the interop file (short of interoperability) could be improved in terms of interacting with the DOM, but still for us the Elm’s benefits are well worth the small hacks we had to do. I believe that as Elm matures those interactions would be even easier.
Here are for example the integration parts of Leaflet’s map under the Events page - the Elm code, and the JS interop one.
Gulp
There are too many tools out there, and we have chosen Gulp. We’ve built an opinionated Gulp file that takes care of compiling Elm, building SASS, deploying to gh-pages, etc.
The nicest part of it for development is having the error messages appearing both on the terminal, and on the screen upon live reload.
The good news in case you’d like to use it, is that it’s part of the above mentioned Yo generator.
What’s Next
I have tagged a v1.0.0
, but like any other software, it just marks a milestone - we are constantly improving and polishing parts of it. As always, if you spot anything you’d like to change or add your pull requests are most welcome!