Webpack 3 and Phoenix 1.3

Phoenix 1.3 changes the asset directory structure which results in most tutorials and examples for adding elm or webpack to phoenix obsolete.

This post shows an example configuration that uses Elm 0.18, Phoenix 1.3 and Webpack 3. It is used in FreeChat a modern Elixir/Elm IRC Client.

Elm code is placed in assets/elm (just like JavaScript code is in assets/js/). Thus directory is assumed in the following webpack configuration.

Setting up Webpack and Elm

Generate your phoenix app

Example:

mix phx.new free_chat
cd free_chat
mix ecto.create

Remove brunch-config.js

rm brunch-config.js

Add webpack to phoenix config (to automatically compile assets)

add watchers: [npm: ["start", cd: Path.expand("../assets/", __DIR__)]] to your config/dev.exs

Add Webpack configuration

webpack.config.js

const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const elmSource = __dirname + "/elm";
const env = process.env.MIX_ENV || "dev";
const isProduction = env === "prod";
const path = require("path");

module.exports = {
  devtool: "source-map",
  entry: {
    app: ["./css/app.scss", "./js/app.js", "./elm/Main.elm"]
  },
  output: {
    path: path.resolve(__dirname, "../priv/static/"),
    filename: "js/app.js"
  },
  resolve: {
    extensions: [".css", ".scss", ".js", ".elm"],
    alias: {
      phoenix: __dirname + "/deps/phoenix/assets/js/phoenix.js"
    }
  },
  module: {
    rules: [
      {
        test: /\.(sass|scss)$/,
        include: /css/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: [
            { loader: "css-loader" },
            {
              loader: "sass-loader",
              options: {
                sourceComments: !isProduction
              }
            }
          ]
        })
      },
      {
        test: /\.(js)$/,
        include: /js/,
        use: [{ loader: "babel-loader" }]
      },
      {
        test: /\.elm$/,
        exclude: ["/elm-stuff/", "/node_modules"],
        loader: "elm-webpack-loader",
        options: { maxInstances: 2, debug: true, cwd: elmSource }
      }
    ],
    noParse: [/\.elm$/]
  },
  plugins: [
    new ExtractTextPlugin("css/app.css"),
    new CopyWebpackPlugin([{ from: "./static" }])
  ]
};

Add a minimal package.json

assets/package.json

{
  "repository": {},
  "license": "GPLv3",
  "scripts": {
    "start": "webpack --watch --color"
  },
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html"
  },
  "devDependencies": {
    "webpack": "^3.5.5",
    "copy-webpack-plugin": "^4.2.0"
  }
}

Install javascript packages

Run yarn install

To avoid outdated versions the above package.json is almost empty. Please run the following command to add the necessary dependencies:

  yarn add --dev babel-core babel-loader babel-preset-es2015
  yarn add --dev css-loader style-loader extract-text-webpack-plugin
  yarn add --dev node-sass sass-loader
  yarn add --dev elm-webpack-loader

Now the package json should includes all the package and they are already installed as well.

Add a dummy (or minimal) Main.elm

Create a directory for your elm files: mkdir assets/elm

assets/elm/Main.elm

module Main exposing (..)

import Html exposing (text)


main =
    text "hello, phoenix"

Install basic elm packages

cd asset/elm/
elm-package install

This command should generate a elm-package.json if not yet present

Add elm to your app.js

Minimal Elm snippet for your phoenix app.js

import "phoenix_html";
import { Socket } from "phoenix";

const Elm = require('../../elm/Main');
Elm.Main.embed(document.getElementById('elm-main'));

Add a container to your html

To render the elm app in your html you need to add an element with the above id elm-main to your markup.

For the free_chat example it means adding <div id="elm-main"></div> to the index file lib/free_chat_web/templates/page/index.html.eex.

Final steps

With this configuration running mix phx.server will also automatically recompile all assets including your Elm code. Now you can see the compilation result in the console and visit your webapp in the browser. For a full working example take a look at the FreeChat project.

Helpful resources (older webpack or phoenix versions)

  • FreeChat Elixir/Elm app
  • https://www.dailydrip.com/topics/elixirsips/drips/webpack-phoenix-and-elm (webpack 1, phoenix 1.2)
  • https://blog.exertion.io/using-webpack-with-phoenix-and-elm/ (webpack 1, phoenix 1.2)
  • Elm webpack loader