Foreword: this article is from my attic. It was planned to be published in 2024, it's probably a bit old... But I don't think Elm and Phoenix framework changed a lot in 2 years.
I usually use OpenBSD when programming in Erlang, C or Elixir. Unfortunately, doing front-end development with node for example or even doing some back-end stuff in Rust can lead to a disaster. Indeed, OpenBSD is a great piece of software but not everything is working by default, and you will keep hacking around. Hacking is always a good way to learn, but, you don't always have time for doing that. When developing front-end application, but also application for iOS or Android, you should use something highly compatible and with not a lot of bad surprises.
Introduction
Front-end programming is something a bit weird. In fact, front-end development is present in my long list of IT nightmare, alongside with Object Oriented Programming (in particular Java), systemd, blobs and other highly complex framework you can find in system administration.
Anyway, when it comes to design a web-site, it costs me time, to create the Javascript part and the CSS. Because the web is evolving fast -- perhaps too fast -- and because I'm not a front-end developer, my toolbox stayed in the past. How to find stability in this environment? Well, it seems Elm is quite stable. I followed this project since 2016 or 2017 without seeing critical changes in the syntax, that's a very good point. The standard library and external modules are looking stable as well. The only alternative I know that could help to build front-end is clojure-script.
My own projects led me to build web applications and I don't have time to maintain them. Dealing with stability and strict programming rules is a game changer. I don't know if a framework using the same principles than Erlang exists in Front-end world though. Anyway, let integrates Elm in an Elixir stack with Phoenix framework.
Phoenix Project from Scratch
If you already have used Phoenix Framework in the past, no secret there. I assume you already have mix available on your system. You can use any solution for that, like asdf or debian packages.
# install hex locally
mix local.hex --if-missing
# install phx_new templating module
mix archive.install hex phx_new
# create a new project without assets,
# we will add them manually later
mix phx.new project --no-assets
cd project
Elm Project from Scratch
Elm can be installed using npm or directly by downloading on the official website. We will first download it to create the project and then move to npm solution during the integration step.
mkdir -p assets/elm
cd assets/elm
To init a project, elm init command needs to be executed. It will generate a elm.json file and a src folder.
elm init
Based on the different projects I watched before doing that, it seems the convention is to create a src/Main.elm file containing the entry-point.
touch src/Main.elm
This file will contain our dummy application for the moment.
module Main exposing (..)
import Browser
import Css exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
-- This is our main function, our entry-point.
main =
Browser.element
{ init = initialModel
, view = view
, subscriptions = \_ Sub.none
, update = update
}
-- Our model type (our state) will be empty
type alias Model = ()
-- this is our state function
initialModel : () -> ( Model, Cmd Msg )
initialModel _ =
( ()
, Cmd.none
)
-- this is our update (loop?) function, where all events
-- are received
update msg model =
model
-- this is our view function, where all the html/css black
-- magic is created and rendered
view model =
div [] []
Note: Don't forget to install elm-mode in emacs!
Esbuild and NPM Bootstrapping
npm install command can be executing in assets/elm, it will produce a generic package.json file. This one can be modified to fit our needs. elm, esbuild and esbuild-plugin-elm modules are required. Two scripts are also created, one to build the project in one time fashion (build), and the other one watching for modification (watch).
{
"name": "project",
"version": "0.1.0",
"main": "src/index.js",
"license": "MIT",
"dependencies": {
"elm": "^0.19.1-6",
"esbuild": "0.17.11",
"esbuild-plugin-elm": "^0.0.12"
},
"devDependencies": {
"elm": "^0.19.1-6",
"esbuild": "0.19.11",
"esbuild-plugin-elm": "^0.0.12"
},
"scripts": {
"build": "node ./build.js",
"watch": "node ./build.js --watch"
}
}
Phoenix supported esbuild for a long time now, but integrating it with other language like Elm is a bit hard. That's why we will use esbuild API in a small script, instead of hacking Elixir esbuild module. This snippet is not from me, but from esbuild-plugin-elm and Ben Koshy's blog.
const path = require('path')
const esbuild = require('esbuild');
const ElmPlugin = require('esbuild-plugin-elm');
esbuild.build({
entryPoints: [
"./src/index.js"
],
bundle: true,
// outfile: "app.js",
target: "es2017",
outdir: "../../priv/static/assets",
minify: false,
plugins: [
ElmPlugin({
debug: true,
verbose: true,
}),
],
}).catch(_e => process.exit(1))
The last bootstrap is to create a script to link Elm modules in the bundle produced by esbuild. This scrip
import { Elm } from './Main.elm';
// look for an element with the id application set
// and use it as mount point for our Elm application.
var $root = document.getElementById('application');
Elm.Main.init({
node: $root
});
Bootstrapping Phoenix Framework
defmodule Project.MixProject do
use Mix.Project
# ...
defp aliases do
[
# ...
"assets.setup": [
"esbuild.install --if-missing",
"cmd --cd assets/elm npm install"],
"assets.build": [
"cmd --cd assets/elm node build.js"
],
"assets.deploy": [
"cmd --cd assets/elm node build.js --deploy",
"phx.digest"
]
]
end
end
# install esbuild and install node dependencies
mix assets.setup
# build assets
mix assets.build
# build assets and create digest
mix assets.deploy
We should have now a file index.js in priv/assets directory, ready to be injected in our HTML, just add the following code at the end of your page.
<script src={~p"/assets/index.js"}></script>
Development Process
Create your Elm application
Run
mix assets.build, your browser should reboot automatically if changes have been madeModify your Phoenix controllers or views.
Don't forget to create or update
.gitignorefile, you don't want to deal with all assets produced in your repository.
Configuring Backend Hostname
Elm accept flags feature, that means when your application will start it initialization phase, some external arguments will be passed to the entry-point. When starting Phoenix server, this one will usually start to listen on TCP/4000 port. This value can be set as string in flag (or create a JSON object containing all value you want to put there). Based on the previous configuration and bootstrap we did, flags must be set in assets/elm/src/index.js.
import { Elm } from './Main.elm';
var $root = document.getElementById('application');
Elm.Main.init({
node: $root,
flags: "http://localhost:4000/"
});
Then, the arguments passed to the entry-point function in the application must also be modified. Here an example when sending a string.
init : String -> ( Model, Cmd Msg)
init target_ =
( { target = target_ }, Cmd.none )
If an URL is passed, it could probably better to use a JSON object, decoded with Json.Decode, decomposed it, to finally create a new clean and safe Url.
Managing Url with Browser.element
Bernardoow already created an interesting gist on this topic.
Conclusion
It's not so "hard" to use Elm with Phoenix Framework. In fact, except some issue with Esbuild configuration and some odd behaviors with npm, it was just working fine. Many other developers did also the same process - or a similar one.
References and Resources
Awesome Elm: A curated list of useful Elm tutorials, libraries and software. Inspired by awesome list. Feel free to contribute.
The Structure of an Elm Application
Elm, Elixir, and Phoenix: Reflecting on a Functional Full-Stack Project
Elm Destructuring (or Pattern Matching) cheatsheet
Top comments (0)