Skip to content

🌱 A quick starter for building a Phoenix + Elm (PETE Stack) project with Esbuild compilation, live reloading, etc.

Notifications You must be signed in to change notification settings

dwyl/phoenix-elm-starter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Phoenix + Elm Starter

A starter kit for adding an Elm frontend to a Phoenix Web Application.

GitHub Workflow Status codecov.io Hex.pm contributions welcome HitCount

Why?

To build a more advanced UI/UX, there is nothing better than Elm.


What?

A step-by-step guide to getting Elm working in a Phoenix app with live reloading.

By the end of this guide you will understand how all the pieces fit together.

Latest Phoenix, latest Elm and esbuild; the fastest build system available.


How?

Prerequisites

Before you start, make sure you have the following installed:

  1. Elixir: https://elixir-lang.org/install.html
    New to Elixir, see: https://github.com/dwyl/learn-elixir
  2. Phoenix: https://hexdocs.pm/phoenix/installation.html
    New to Phoenix see: https://github.com/dwyl/learn-phoenix-framework
  3. Node.js: https://nodejs.org/en
  4. Elm: https://guide.elm-lang.org/install/elm.html e.g: npm install -g [email protected]

Once you have Elm installed, run the following command in your terminal to confirm:

elm --version

you should see:

0.19.1

Create the Phoenix App

For this example, we are creating a basic Phoenix App without the live dashboard or mailer (email) but with Ecto (Postgres database) so that we can simulate a real-world app.

mix phx.new app --no-dashboard --no-mailer
cd app
mix ecto.setup
mix phx.server

Open your web browser to the URL: http://localhost:4000

You should see the default Phoenix home page:

image

So far so good. πŸ‘Œ
Let's add Elm!

Add Elm to the Phoenix App

Change directory in /assets, and create a directory called elm. Inside the /assets/elm directory, run the following command:

elm init

See: https://elm-lang.org/0.19.1/init

You will see the following prompt:

Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.1/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]: y

Type y and Enter to continue:

Okay, I created it. Now read that link!

That will have created a new directory at /assets/elm/src and an elm.json file.

Create a Main.elm file

Create a new file with the path: /assets/src/Main.elm and add the following contents to it:

module Main exposing (..)
import Html exposing (text)

name = "Alex" -- set name to your name!

main =
  text ("Hello " ++ name ++ "!")

Manually Compile the Elm Code

elm make elm/src/Main.elm --output=../priv/static/assets/elm.js

That results in an un-optimized elm.js file that is 488kb For development/testing purposes this is fine; we can optimize/minify it for production later. (see below)

Let's include this file in our Phoenix template just to show that it works.


Temporarily add elm.js to root.html.heex template

Note: this will not work in production, it's just for basic illustration as a "quick win".

Open the lib/app_web/templates/layout/root.html.heex file and add the following lines just before the </body> element:

<script type="text/javascript" src={Routes.static_path(@conn, "/assets/elm.js")}></script>
<script>
    const $root = document.createElement('div');
    document.body.appendChild($root);

    Elm.Main.init({
        node: $root
    });
</script>

With those lines added to the root.html.heex file.

Run mix phx.server again and refresh your browser: http://localhost:4000/

You should see something similar to the following:

phoenix-elm-hello-alex

That Hello Alex! was rendered by Elm.

Now that we know it can work the hard way, let's do it properly!

undo those changes made in root.html.heex and save the file.


Compile Elm via esbuild

Next we will include the elm app into the esbuild pipeline so that:

  1. We can have a watcher and hot reloader.
  2. Phoenix can handle asset compilation during deployment.

1. Install esbuild-plugin-elm

In the /assets/elm directory, run the following command to install esbuild-plugin-elm

npm install -D esbuild-plugin-elm

2. Create the "initialization" index.js file

Create a file with the path assets/elm/src/index.js and add the the following code:

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

const $root = document.createElement('div');
document.body.appendChild($root);

Elm.Main.init({
  node: $root
});

Ref: phenax/esbuild-plugin-elm/example/src/index.js

3. Create build.js file

Create a new file with the path: assets/elm/build.js and add the following code to it:

const esbuild = require('esbuild');
const ElmPlugin = require('esbuild-plugin-elm');

const isProduction = process.env.MIX_ENV === "prod"

async function watch() {
  const ctx = await esbuild.context({
    entryPoints: ['src/index.js'],
    bundle: true,
    outfile: '../js/elm.js',
    plugins: [
      ElmPlugin({
        debug: true
      }),
    ],
  }).catch(_e => process.exit(1))
  await ctx.watch()
}


async function build() {
  await esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    minify: true,
    outfile: '../js/elm.js',
    plugins: [
      ElmPlugin(),
    ],
  }).catch(_e => process.exit(1))
}

if (isProduction)
  build()
else
  watch()

Ref: phenax/esbuild-plugin-elm/example/build.js

4. Add the Build Command / Watcher to dev.exs

Open the config/dev.exs file and locate the watchers: section. Add the following line the list:

node: ["./build.js", "--watch", cd: Path.expand("../assets/elm", __DIR__)]

5. Import the compiled Elm (JS) Code in app.js

Open the assets/js/app.js file and add the following lines near the top of the file:

// import the compiled Elm app:
import './index.js';

e.g: app.js#L28


With all 3 files saved, run the Phoenix server:

mix phx.server

You should see output similar to this:

[info] Running AppWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[info] Access AppWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
Success!

    Main ───> /var/folders/f2/3ptgvnsd4kg6v04dt7j1y5dc0000gp/T/2022327-49759-ua0u9f.iqdp.js

[watch] build started (change: "js/index.js")
[watch] build finished

That confirms that the Elm build + watchers are working. πŸš€

6. Test Live Reloading!

With the Phoenix server running, and a browser window open pointing to the Phoenix App: http://localhost:4000

elm-hello-alex

Open the assets/elm/src/Main.elm file and change the line:

name = "Alex"

to:

name = "World"

When you save the file it will automatically reload in your web browser and will update the name accordingly:

elm-hello-world

So the watcher and live reloading is working!


This is still very far from being a "real world" App. But the "starter" is here!


Todo

  1. "Productionize" the asset compilation: https://hexdocs.pm/phoenix/asset_management.html#esbuild-plugins

@SimonLab if you have time to help extend, please go for it! πŸ™

  1. Add Elm Test!

Next

Create a Phoenix Endpoint that returns json that can invoked from Elm.
e.g: Return an inspiring quote
Borrow from: https://github.com/dwyl/phoenix-content-negotiation-tutorial


Recommended / Relevant Reading


About

🌱 A quick starter for building a Phoenix + Elm (PETE Stack) project with Esbuild compilation, live reloading, etc.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published