Skip to main content
Hyperglot Programmer
side navigation

Embedding Elm in Phoenix

I am setting up a new project that will be using Phoenix and Elm together, so I am taking this opportunity to document the process to make it easier for others to set up as well. Maybe I should turn this into a mix 'new'able template sometime...

Initial Setup

Note

If you are adding elm to an existing phoenix project then feel free to skip this step.

First things first, make sure you already have nodejs (6+), npm, Elm, Erlang, Elixir, and Phoenix installed.

So lets start with a basic phoenix app, will be using normal integer IDs in the model, using brunch (webpack is so slow in my testing in comparison), and just fairly stock. Let's name it, oh, MyServer. So let's run this in your shell of choice:

$ mix phoenix.new my_server --module MyServer
# Snip a ton of stuff as it creates the files...

Fetch and install dependencies? [Yn] Y
* running mix deps.get
* running npm install && node node_modules/brunch/bin/brunch build

We are all set! Run your Phoenix application:

    $ cd my_server
    $ mix phoenix.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phoenix.server

Before moving on, configure your database in config/dev.exs and run:

    $ mix ecto.create

$ cd my_server # Make sure to go in to the directory if you edit here

Make sure to have it fetch and install dependencies to prepare the node files by answering Y to the question

At this point it should also be good to set it up as a git repo, it is never too early to make your repository:

1
2
3
4
5
6
$ git init
Initialized empty Git repository in /home/<user>/elixir/my_server/.git/
$ git add .
# Snip ton of files added to your new git repo
$ git commit
# Snip ton of files committed to your repo

Other than making sure your database is setup if you are using one and so forth, that should be all the basics. Now let's add elm to the project.

Adding Elm to Phoenix via brunch

First we need to actually have the project get and manage elm, so open your ./package.json file and go to the "devDependencies" section and add a new elm entry such as:

"elm-brunch": "~0.7.0",

This will handle downloading and installation of elm-brunch and elm itself. If you wish you can run npm install now to get it, otherwise phoenix will get it for you when we run it later.

Next we need to open the brunch-config.js file. Go to the exports.config.paths.watched section. You will want to add a new a new path for where-ever you want to put your elm source files. I prefer them to go in to web/elm so that is what I am adding to that section:

"web/elm",

We should, though not need to, but I highly recommend adding an elmBrunch configuration inside your brunch-config.js file. To do so continue down to the exports.config.plugins section and we need to add a new section after your babel section (as always don't forget any commas on newlines as appropriate). Let's start with this configuration:

elmBrunch: {
        // Set to path where `elm-make` is located, relative to `elmFolder`
        // (optional)
        // executablePath: './node_modules/elm/binwrappers',

        // Set to path where elm-package.json is located, defaults to project root
        // (optional)
        elmFolder: ".",

        // Set to the elm file(s) containing your "main" function
        // `elm make` handles all elm dependencies (required)
        // relative to `elmFolder`
        mainModules: [
                'web/elm/Main.elm'
        ],

        // Defaults to 'js/' folder in paths.public (optional)
        // However for phoenix we want it to be combined with the app.js file so do:
        outputFolder: "web/static/js",

        // If specified, all mainModules will be compiled to a single file
        // (optional and merged with outputFolder)
        // This is likely what we want to do with Phoenix for web efficiency
        outputFile: "elm.js",

        // optional: add some parameters that are passed to elm-make
        // "--warn" reports on important warnings so they do not go invisible,
        // I always recommend it.
        makeParameters: [
                "--warn"
        ]
}

The inline comments should be able to describe what is going on, and keep in mind that any 'root' Main program you may have in Elm should be listed in mainModules so they all get combined into one file to share the elm standard library and save a ton of space.

Setup Elm Itself

Setting up elm itself just involves setting up its package file, to do that make a file named elm-package.json and populate it with:

{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "."
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-lang/core": "4.0.5 <= v < 5.0.0",
        "elm-lang/html": "1.1.0 <= v < 2.0.0"
    },
    "elm-version": "0.17.1 <= v < 0.18.0"
}

The version must always start out with 1.0.0, enforced by much of Elm itself. Alter the summary, repository, and license as makes sense, but keep summary to less than 80 characters, this will also error out at you in Elm if too long. The source-directories must contain a list of only "web/elm" or where-ever you chose to place it, or else it can try to compile a lot more than you expect. The "exposed-modules" is only used if you are making a distributable Elm library, which not really for this project. The two listed dependencies are the usual to get started with a basic Elm program, like a Counter. And finally the "elm-version" should be self-explanatory, all versioning in Elm is semver and enforced by the Elm system.

Next, though optional, I would highly recommend adding the following line to your .gitignore file to ignore the elm cache directory:

/elm-stuff/

With these are we are now ready to create the first Elm file and hook it into the application.

Creating our first Elm application in Phoenix

Create the file web/elm/Main.elm or of whatever file you put in your mainModules section and open it up. Let's go ahead and do the usual Elm counter app so we can make sure that it works and so that we have something to wire up:

module Main exposing (..)

import Html exposing (Html, button, div, text)
import Html.App as Html
import Html.Events exposing (onClick)


main : Program Never
main =
    Html.beginnerProgram { model = model, view = view, update = update }



-- MODEL


type alias Model =
    Int


model : Model
model =
    0



-- UPDATE


type Msg
    = Increment
    | Decrement


update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            model + 1

        Decrement ->
            model - 1



-- VIEW


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , div [] [ text (toString model) ]
        , button [ onClick Increment ] [ text "+" ]
        ]

This is the standard beginner counter app from the Elm docs and will work wonderfully for testing the integration with the Phoenix system.

At this point, since I like intellisense in my IDE's, go ahead and compile this all by running mix phoenix.server to start the server and let brunch build it all, make sure you can access the main web page. You will notice that if you save the Main.elm file again then phoenix will auto-rebuild it for you since the server is running in dev mode and we adjusted the brunch watch path to include the elm directory. Go ahead and save the Main.elm file at least once now to make sure it recognizes it and rebuilds it, and to make sure we get the web/static/js/elm.js file generated. It is good to have this file in this directory as Elm fits into the ES6 module system fine and so it will be properly optimized and minimized by brunch in release deploys.

Embed Elm in a Phoenix Page

In general the elm.someApp.fullscreen should not be used, all it does is just do elm.someApp.embed(document.body), and if you want to add it to the body then explicitly doing so is better anyway. For now let's go ahead and add it to our index page of this default app so open up web/templates/page/index.html.eex, let's wipe it out and replace it all with just this:

<div id="counter-app"></div>

A nice simple div with an id of whatever you want to hook to. Next open your web/static/js/app.js and at the bottom of it add this:

1
2
3
4
5
6
import Elm from "./elm.js"

let counterDiv = document.getElementById("counter-app")
if (counterDiv !== undefined) {
        Elm.Main.embed(counterDiv)
}

Inside the if is a good place to register ports and such as well if you need any for your app. But right now you should be able to load your browser to the main index page and see your counter. I seem to be able to get around 100 clicks per second with my mouse-macro on my system. :-)

Ending Notes

From this point on you can continue to grow the app, add more apps, etc. If you make multiple main apps, say MyMain.elm and AnotherMain.elm then you would just embed them respectively via Elm.MyMain.embed(whateverDiv) and Elm.AnotherMain.embed(anotherDiv). It is quite easy to expand as you so need. If you want one app per page and do not want to combine all the javascript together then you can disable the combining into a single Elm file option and use the normal Phoenix/Brunch stuff to make multiple top-level javascript files. I personally prefer everything in one main big file as it reduces the overall storage and download size, but it might be better to split it if you have a lot of very large apps on a lot of pages, otherwise I'd say do not worry about it.

If there is anything that I should clarify or if the standards change with Elm then please notify me and I will attempt to answer any question as best I can. :-)

Comments

Comments powered by Disqus