Serving Elm Assets From a Phoenix App
Putting the Pieces Together
Deploying the Backend to Heroku
I found that deploying the backend of an Elixir/Phoenix application to Heroku was quite simple. The instructions provided by Heroku work right out of the box. Once I had this done, I could run a local copy of my Elm app and have it talk to the Phoenix server running on Heroku (just be use to use a CORS browser plugin). This is a good proof of concept, but to go to production, I need to deploy the front end Elm app as well as the Phoenix API.
Deploying the Front end
In order to deploy the front end, I need two things.
- A static page served by Phoenix
- An assets pipeline that compiles my Elm code and creates a JavaScript bundle to be used by that static page
We’ll now walk through the steps required to deploy the Elm front end along with our Phoenix API.
Application Directory Structure
The directory structure you use to build a Phoenix and Elm project is important. In order to easily deploy to Heroku, you want the Phoenix project to be at the top-level of your hierarchy. You then need to create a subdirectory containing the Elm code. In my case, I call this directory elm. So, a partial view of the directory structure looks like this.
my_phoenix_app
elm
elm-package.json
node_modules
src
Main.js
web
lib
priv
static
js
Brunch Config File
Brunch is the npm-based asset management tool that ships with Phoenix. The brunch tool is used to compile all assets required for deployment. In order to have brunch compile your Elm , code you need to add the elm-brunch plugin.
First, install the plugin.
npm install --save-dev elm-brunch
Then you can enable this plugin by editing the brunch-config.js file that is created as part of Phoenix project creation. You need to add two elements.
- Add the relative path of the directory containing your Elm source code to the paths.watched list.
// brunch-config.js
paths: {
// Dependencies and current project directories to watch
watched: [
"web/static",
"test/static",
"elm/src"
],
- Add the elmBrunch entry to the plugins object. Here we include paths to our Elm directory, the Elm executables path, a list of top-level modules to compile, and an output directory path. The example below works with the directory structure described above.
// Configure your plugins
plugins: {
babel: {
ignore: [/web\/static\/vendor/]
},
elmBrunch: {
executablePath: '../node_modules/elm/binwrappers',
elmFolder: 'elm',
mainModules: ['src/Main.elm'],
outputFolder: '../priv/static/js',
outputFile: 'bundle.js',
makeParameters: ['--warn']
}
},
Once you have this setup, if you start the Phoenix server, you should see this. This indicates that you are creating the compiled Elm bundle.
Elm compile: src/Main.elm, in elm, to ../priv/static/js/bundle.js
Phoenix Static Page Setup
A new Phoenix project comes set up with a static index page in web/templates/page/index.html.eex. If you’d like your Elm app to take ver a portion of the page you can add a div containing the Elm app anywhere on the page. Alternatively, you can simply remove the existing content and replace it with a page that will server your Elm code. Here is the code snippet I use to make this happen.
<!-- web/templates/page/index.html.eex -->
<script type="text/javascript">
var elmDiv = document.getElementById('elm-app');
Elm.Main.embed(elmDiv);
</script>
Including the Elm JavaScript
The final step to the process is to include the JavaScript produced by Elm and Brunch into our HTML. In the default Phoenix app, the root HTML is stored in web/templates/layout/app.html.eex.
<!-- web/templates/layout/app.html.eex -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content=""> <title>Hello PhoenixElmQuickstart!</title>
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
<script type="text/javascript" src="js/bundle.js"></script>
</head>
The last line of the <head> section is the addition. You simply tell your page to look for the generated JavaScript code in js/bundle.js. That’s really it. At this point if you navigate to localhost:4000, you should see your Elm code properly rendered.
Summing it Up
I love the idea of having a relatively simple backend API that seamlessly talks to a very rich, interactive web front end. The combination of Elm and Elixir gives me a wonderful functional environment on both ends. Phoenix combined with the brunch asset management system makes it easy to make this all play nicely together.
You can find a copy of this code in my git repository.