Adding Elm to a Rails 5.1 application

Note: Webpacker update

Although this tutorial will still work it has been superseded by the addition of Elm to the webpacker gem. See this commit and the entry in the README. The end result is nearly the same.

Adding Elm to a Rails 5.1 application

The release of Rails 5.1 (http://weblog.rubyonrails.org/2017/4/27/Rails-5-1-final/)has brought some interesting changes for frontend development. Most notably is that it comes with yarn, a tool to manage your javascript dependencies from NPM. It resembles Bundler so should be familiar to any Rails developer. It has made my life a lot easier at least.

Secondly, it has been made easier to integrate Webpack into your Rails development workflow using the webpacker gem. There have been several gems to provide similar functionality but this one can be automatically configured when you create a new Rails project. Support for VueJs, React and Angular is available, but Elm is lacking right now. I’ll show how you can integrate Elm with Rails 5.1 and Webpack.

We’re gonna start from scratch and create a new Rails project with Webpack support.

$ rails new rails51-elm-example --webpack

This will generate the new project and run yarn to install all npm dependencies.

Next we’ll create a simple home controller to render a page.

$ rails g controller home index

And, we will modify routes.rb to point to the new controller action

Rails.application.routes.draw do
  root to: "home#index"

  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

Running rails s and visiting http://localhost:3000 should render a simple page with the following text:

Home#index

Find me in app/views/home/index.html.erb

We now have a simple rails app setup. Next we will install the elm npm dependencies using yarn, as well as add the webpack loaders for elm.

$ yarn add elm elm-hot-loader elm-webpack-loader

Elm comes with its own package manager elm-package and to proceed we need to install the default Elm packages. Because this package manager is not in our path, but resides under ./node_modules/.bin/ we can use yarn run to execute it.

$ yarn run elm-package install

You will be asked whether to execute the plan of dependencies to be installed. Press Y to continue. The command will create elm-package.json to list your elm-dependencies and an elm-stuff directory. This directory can be added to .gitignore so we don’t track it in our version control.

echo "/elm-stuff" >> .gitignore

Integrating Elm with Rails

So, we have a working Rails installation and we have installed Elm, all that is left is make them work together. Remember we installed the webpacker when we created the new rails application. This created a whole bunch of files in the config/webpack directory. This stuff is new in Rails. If you are familiar with Webpack you’ll know how most webpack.config.js files look, it’s a big object with loaders, entry points, outputs, plugins etc. and you can chain and hook up all kinds of asset-loaders. We are working with basically the same config file here only it looks a little different.

Much like the usual Rails configuration files there is configuration for each kind of environment (production.js/development.js/testing.js), as well as a shared configuration file shared.js that has configuration common for all environments. If you know Webpack you’ll recognize the configuration in these files.

There is also a directory loaders in the directory that comes with several preconfigured loaders. Usually the loaders are in an array in the webpack configuration object, here each loader gets its own file in this directory. There are loaders for coffeescript/babel and erb for example. In a moment we will add a loader for elm-files.

But first, apart from the loaders there is a paths.yml that has several configuration options available, and we’ll need to adjust this so files with the .elm extension are matched by Webpack. Add .elm to the extensions array in paths.yml

...
  extensions:
    - .coffee
    - .elm
    - .js
    - .jsx
    - .ts
...

Now we need to add a loader for the .elm files. Add the file elm.js to config/webpack/loaders.

// elm.js
module.exports = {
    test: /.elm$/,
    exclude: [/elm-stuff/, /node_modules/],
    loader: 'elm-hot-loader!elm-webpack-loader?verbose=true&warn=true&pathToMake=node_modules/.bin/elm-make'
}

This loader does several things, we ensure it matches .elm files but exclude the elm-stuff and node_modules directory. Then we chain two loaders, the elm-hot-loader handles hot module replacements, and the elm-webpack-loader handles the loading of Elm. Note that we pass in the argument pathToMake=node_modules/.bin/elm-make to point the loader to the elm-make program.

We are now nearly done.

You may be used to Rails placing the javascript assets in the app/assets/javascripts directory. This is different when you use the webpacker gem, as the assets are now in app/javascripts/packs. In fact, during installation a file named application.js has been placed there already. You can link to these in your html using the javascript_pack_tag method in your views.

We will add link to the application.js from your layout file in app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Rails51ElmExample</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>
  </head>

  <body>
    <%= yield %>
  </body>
  <%= javascript_pack_tag 'application' %>
</html>

We have now linked to the webpack compiled application.js file. However, since webpack serves the compiled files on a different server we will need to start it to access it. The webpack dev server is binstubbed in your bin folder as ./bin/webpack-dev-server. Remember, this server runs concurrent to your rails server. So, you’ll need to run rails server as well.

If you open http://localhost:3000/ now you will see Hello World from Webpacker in your console.

Last steps

However, we want to see a basic Elm app in our application. Add a file app/javascripts/packs/elm/Main.elm in your app with the following contents

import Html exposing (text)

main : Html.Html msg
main =
  text "Hello from Elm!"

This is a simple elm app. Our last step is to ensure the Elm app is required and mounted onto our html in the application. Change the app/javascripts/application.js by adding these lines.

var Elm = require('./elm/Main.elm');
window.addEventListener('DOMContentLoaded', function(){
    Elm.Main.embed( document.getElementById( 'main' ) );
})

And add the mounting point to the app/views/home/index.html.erb file.

<div id="main"></div>

If you run the webpack dev server and the rails server now you should be able to see Hello from Elm! if you go to http:localhost:3000, this means Elm is succesfully working for this Rails app.

The end result and all the steps can be seen on (https://github.com/maartenvanvliet/rails5.1-elm-example)

A lot of help for this post came from (https://blog.abevoelker.com/2016-09-05/adding-elm-to-a-rails-application/), though the release of Rails 5.1 with the webpacker gem made some alterations necessary.

Photo by Johannes Plenio on Unsplash