An empty open office plan

Setting Up a Development Environment in Elm

  • Kristofer Giltvedt Selbekk

As I was building out yet another Elm test project, I wanted to add some CSS to my app. I added a styles.css file to my source folder, and soon realized I had nowhere to include it! I've always been running elm reactor up until this point, but that doesn't give me access to any styles whatsoever.

After looking in the docs, I found I could use elm make to create a default HTML file for me, but that meant that I had to build my project on every change to iterate on my application. Surely, there's a better approach out there?

Turns out, the options weren't documented very well, but they're definitely out there! This article will show you a few different ways you can set up a development environment for a real world application, complete with hot reloading and all.

Getting started: Hello again, NPM

The first thing you'll need to do, is to run npm init (alternatively yarn init) to bootstrap a package.json file. I thought I wouldn't have to use a JavaScript package manager for Elm apps, but in order to set up a development environment, you definitely need to.

In any real world application, you'll probably need some JavaScript libraries at some point, too, so don't feel bad about adding it. You'll still be writing Elm most of the time!

The naïve approach - automation!

The simplest approach I could come up with is to set up a file watcher that runs elm make --output public/app.js on every change in my Elm files. I went with watch, an npm package, and added the following to my package.json:

{
  "scripts": {
    "start": 
      "watch \"elm make src/Main.elm --output=public/app.js\" src/**/*.elm"
  } 
}

Now, I can create an index.html file in the public/ directory, and add my CSS file next to it. When I'm done, my file structure will look something like this:

src
 - Main.elm
 - OtherFiles.elm
public
 - index.html
 - styles.css
 - app.js
package.json
elm.json

The index.html file is super basic, and will look like this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>My Elm app</title>
    <link href="./styles.css" rel="stylesheet" />
  </head>
  <body>
    <div id="root"></div>
    <script src="app.js"></script>
    <script>
      Elm.Main.init({
        node: document.getElementById("root")
      });
    </script>
  </body>
</html>

Now, I can open the public/ folder with i.e. serve, and iterate on my app like it ain't no thing.

The Elm approach: elm-live

Even if the above approach technically works, it feels like I've built something with toothpicks and band-aids. Surely, there's something a bit more put together?

Luckily, there is. elm-live is the all-Elm alternative that provides us with iterative builds, hot module reloading and a ton of other features.

To get started, install it with npm or yarn:

npm install elm-live
yarn add elm-live

Next, we need to add a script to our package.json file:

{
  "scripts": {
    "start": 
      "elm-live src/Main.elm --hot --start-page=public/custom.html -- --output=public/app.js"
  } 
}

And that's it! Start your script, and a hot-reloading dev server will pop up and instantly improve your developer experience.

My favorite approach: Parcel

Although the elm-live approach is nice and all, we still need to have a bunch of manually edited stuff inside of our public/ folder. Wouldn't it be nice if some build tool figured all of this out for us?

Meet Parcel. Parcel is a bundler for web apps, which happens to support Elm out of the box. In addition, it works with JavaScript, CSS and other resources as well, which will help us a lot down the road. Let's give it a try!

First off, let's install it with your package manager of choice.

npm install parcel-bundler
yarn add parcel-bundler

Next, move your hand-crafted public/styles.css and public/index.html into your src folder. They're source code, after all! We'll create an additional file, index.js, which will look like this:

import { Elm } from './Main.elm';
import './styles.css';

Elm.Main.init({
  node: document.getElementById('root')
})

This lets us remove the inline script tag from our HTML file, as well as the <link /> CSS tag.

Finally, let's add the script to our start tag:

{
  "scripts": {
    "start": "parcel src/index.html"
  } 
}

There's nothing more to it. Run your script, and you'll notice a hot-reloading dev server pop up for you, just like you'd expect. This will even include an Elm debugger tool as well 😎

A nice bonus feature with Parcel, is that you'll be able to add styles processing and JavaScript transpilation in no time at all. That's nice to know!

That's it!

Turns out, setting up a nice developer experience isn't a lot of work, once you know what to do. I hope this article can help future beginners like myself looking for the best way to get started.