Phoenix & JSPM

One of the things you start out with when you create a new Phoenix application is the default brunch setup for JavaScript. Since I’m not a big fan of manually copying dependencies for JS around, I opted for Brunch, due to its automatic integration with brunch and relative ease of use. Since Brunch isn’t being developed anymore, I began searching for an alternative that provides the same functionality.

JSPM just seems to fit the bill. It not only is built with the browser as top priority, but also already meant to be used via HTTP/2, which sadly hasn’t arrived yet in Cowboy, the server Phoenix uses (but they plan to release it towards end of the year, so keep your hopes up).

For sake of demonstration we’ll just use sqlite, though we could just as well use --no-ecto, I just wanted this to resemble an actual application as much as possible.

Installation in a new project without brunch

First we’ll cover how you can install JSPM in a new Phoenix application created with --no-brunch so you can see what is neccesary to add to get JSPM integrated.

mix phoenix.new brunchless --database sqlite --no-brunch
cd brunchless
mix ecto.create
mix phoenix.server

You should be seeing the good old default startpage of a new Phoenix application. So let’s get really started by installing JSPM. In case you don’t have NPM yet, it’s included in the Node.js JavaScript runtime.

Now we’ll need a file to store our packages installed via npm, which in this case will only be executables we use to compile files and JSPM itself. To create a full package.json simply run this command.

npm init --yes

We use the -S flag to install JSPM only for this project and add it to the dependencies.

npm install -S jspm

If you haven’t done so already, you might also want to add ./node_modules/.bin to your $PATH to make finding the jspm executable easier.

Now that the boring stuff is out of the way, we can finally get to using JSPM.

jspm init -p
Would you like jspm to prefix the jspm package.json properties under jspm? [yes]:
Enter server baseURL (public folder path) [./]:./priv/jspm/
Enter jspm packages folder [priv/jspm/jspm_packages]:priv/jspm/pkg
Enter config file path [priv/jspm/config.js]:
Configuration file priv/jspm/config.js doesn't exist, create it? [yes]:
Enter client baseURL (public folder URL) [/]:/jspm
Do you wish to use a transpiler? [yes]:
Which ES6 transpiler would you like to use, Babel, TypeScript or Traceur? [babel]:

This will put all things related to JSPM into the /priv/jspm directory, which will come in handy when we define the static paths later.

Next up comes defining the static paths for Elixir: In lib/brunchless/endpoint.ex just below the other Plug.Static, add following plug:

plug Plug.Static,
  at: "/jspm", from: {:brunchless, "priv/jspm"}, gzip: false,
  only: ~w(js pkg config.js)

This will serve requests from the browser at /jspm to our ./priv/jspm and allow us to serve all our JavaScript from there. I usually also delete the js entry from the plug Plug.Static at: "/" above, but that’s optional since we didn’t call our path js. Just keep in mind that anyything written to ./web/static/js and ./web/static/vendor won’t be served at all since it’s not being copied to ./priv/static/js by Brunch.

Something that I haven’t found a good solution for yet is the phoenix_html.js inclusion. For now it’s best to copy the file from ./deps/phoenix_html/priv/static/phoenix_html.js to ./priv/jspm/js/phoenmix_html.js. But I’d love any sugggestions regarding this hack, maybe adding a static plug just for it?

Next I’m taking you out for a little spin with:

jspm install spin

And to use the spinner, create ./priv/jspm/js/app.js with following code:

import './phoenix_html';
import Spinner from 'spin';

var target = document.getElementsByClassName("logo")[0];
var spinner = new Spinner({lines: 8}).spin(target);

This is simply menat to work with the default Phoenix layout and should display a spinner in the middle of the page. To actually load JSPM we also need to replace the <script src="<%= static_path(@conn, "/js/app.js") %>"></script> in ./web/template/layout/app.html.eex with this:

<script src="<%= static_path(@conn, "/jspm/pkg/system.js") %>"></script>
<script src="<%= static_path(@conn, "/jspm/config.js") %>"></script>
<script>System.import('js/app.js')</script>

That might look like a lot, but for production we’ll be able to replace it with a single line. This simply allows us to dynamically reload the files without any compilation time and speed up development compared to the old Brunch setup.

At this point we can already check in the browser that everything’s working correctly: Hello Phoenix with Spinner

For reloading, we still need to tell Phoenix the path to our JSPM folder. In order to do that, change the ./config/dev.exs section for :live_reload to look like this:

# Watch static and templates for browser reloading.
config :brunchless, Brunchless.Endpoint,
  live_reload: [
    patterns: [
      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
      ~r{priv/jspm/.*js$},
      ~r{web/views/.*(ex)$},
      ~r{web/templates/.*(eex)$}
    ]
  ]

Restart the server and try changing the number of lines for the Spinner. It should automatically reload and show the updated version.