Updates

From the time I wrote part 1

of this short series, Atom has gained a new Elixir plugin based on Samuel Tonini’s Alchemist Server.

From the Emacs plugin, it inherits all the most notable features such as

autocomplete, jump to definition/documentation for the function/module under

the cursor, quote/unquote code and interactive macro expansion.

A feature reference along with some screenshots can be found at the atom-elixir page.

It also looks pretty good.

The assets pipeline

Assets pipelines are one of the most important features in modern web frameworks.

When working on this task, Phoenix developers have proven that they value

pragmatism over purity and have chosen to base their implementation on Brunch, a Node.js build tool that takes care of everything

related to assets management.

This choice has probably saved man-years of work, that would have inevitably delayed the release of a fully working pipeline system.

A very common counter argument is that this adds node as a dependency, but I

think it’s a negligible inconvenient, node is most probably already present on

the majority of developers machines.

Brunch installation is just a npm install away and it runs automatically when

you create a new Phoenix project

$ mix phoenix.new brunch_demo * creating brunch_demo/config/config.exs * creating brunch_demo/config/dev.exs ... Fetch and install dependencies? [Yn] y * running mix deps.get * running npm install && node node_modules/brunch/bin/brunch build

As you can guess, the local brunch is installed in node_modules/brunch/bin/brunch .

You can install it globally with the usual -g flag: npm install -g brunch .

Phoenix automatically runs Brunch when assets change.

If you launch mix phoenix.server and make some changes to web/static/js/app.js you can see that Brunch is working in the background.

[info] Running BrunchDemo.Endpoint with Cowboy using http on port 4000 28 Apr 13:31:56 - info: compiled 5 files into 2 files, copied 3 in 2.2 sec 28 Apr 13:33:08 - info: compiled app.js and 3 cached files into app.js in 118ms

Defaults

By default Brunch watch for changes in css and js folders inside web/static and automatically recompile and package them for HTTP serving.

It is configured to work with ES6 and transpile it to ES5 through

Babel, so you can start using ES6 today, without hassles.

Javascript files are automatically wrapped into a module, that needs to be required

before being used.

Every file inside web/static/js will be converted and loaded on demand.

// web/static/js/app.js export var App = { run: function(){ console.log("Hello from Phoenix!") } }

<!-- web/templates/layout/app.html.eex --> <script>require("web/static/js/app").App.run()</script> <!-- shows "Hello from Phoenix!" in the browser's console -->

If you have legacy code that won’t work if modularized or doesn’t need modularization,

you can put it in web/static/vendor and it will be copied as it is.

This is also the easier way to create global variables

// web/static/vendor/globals.js global_variable = "this is global"; // open up the console and type global_variable // can you guess the result?

Any of this default can be changed in brunch-config.js .

For example this is the part that ignores the module wrapping for the vendor folder.

plugins: { babel: { // Do not use ES6 compiler in vendor code ignore: [/web/static/vendor/] } },

Last but not least, Phoenix automatically loads

Brunch’s bootstrapper code which provides module management and require() logic

code which provides module management and require() logic Phoenix Channels JavaScript client (deps/phoenix/web/static/js/phoenix.js)

Some code from Phoenix.HTML (deps/phoenixhtml/web/static/js/phoenixhtml.js)

Plugins

Brunch is configured to load a numbers of plugin, specifically

javascript-brunch: enables processing of Javascript files

babel-brunch: the ES6 transpiler

uglify-js-brunch: Javascript minifier

css-brunch: enables processing of CSS files

clean-css-brunch: CSS minifier

Plugins are installed through npm , for example if you wanna use Coffeescript in

your application you can simply npm install --save coffee-script-brunch and

your coffee files will be automatically picked up and processed.

Tool belt

Manually copying files or libraries inside the vendor folder is not exactly the

best way to handle external dependencies.

One of the features of Brunch is that it allows the developers to take advantage of the Node ecosystem.

Brunch works seamlessly with Bower, which IMHO is the simplest way to handle

third-party frontend dependencies.

Do you need underscore ?

Just bower install --save underscore , (restart the server if the files are not being compiled automatically) and type _ in the console

function _(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }

One problem I’ve found is that not every folder provided by the packages is being copied (or concatenated) in the output folder (usually priv/static unless you changed it).

I was working with an old version of materialize and the font was not being loaded.

The solution was pretty easy, just open up lib/<app_name>/endpoint.ex and look for this line

plug Plug.Static, only: ~w(css fonts images js favicon.ico robots.txt)

In my case materialize was using font as a folder name, but it wasn’t whitelisted

in the Static Plug configuration. I added font to the list and it fixed the issue.

There’s more than a way

One thing that the Phoenix framework does and does really well is not being

strongly opinionated.

Brunch is just the default assets manager, but it’s really simple to use another one, just change this line in dev.ex

watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin"]]

There’s an example in the Pheonix documentation on how to use Phoenix with webpack, I’m gonna go further, I’ll show you how to write the skeleton of an assets manager and get it started by Phoenix.

Create a new file in lib/watcher.exs (exs means Exlixir script) and paste this code inside it

# lib/watcher.exs defmodule Watcher do def start do IO.puts "* Start monitoring #{Path.absname("web/static")}" IO.inspect System.argv IO.inspect System.get_env end end Watcher.start

and then change the configuration this way

watchers: [mix: ["run", "lib/watcher.exs", "random input"]]

and start the Phoenix server

mix phoenix.server [info] Running BrunchDemo.Endpoint with Cowboy using http on port 4000 * Start monitoring <app_path>/web/static ["random input"] %{"CLICOLOR" => "1", "PROMPT_COMMAND" => "_update_ps1; update_terminal_cwd", "_system_arch" => "x86_64", "DISPLAY" => "/private/tmp/com.apple.launchd.4Zqdr48hnr/org.macosforge.xquartz:0", ...

Ok, it works but it’s not really useful, we’re gonna add a new feature, that watches

for file changes inside the web/static folder and logs to the screen.

First we’re gonna need to install a filesystem watcher component, there is one

written in Erlang that we can import directly from Github.

Add this line to mix.exs

# mix.exs defp deps do # ... {:fs, github: "synrc/fs", override: true}] # override tells the Elixir compiler that this package overrides the default # :fs Erlang module end

fetch the library with mix deps.get

and then update the watcher.exs code

# lib/watcher.exs defmodule Watcher do def start do IO.puts "* Start monitoring #{Path.absname("web/static")}" IO.inspect System.argv IO.inspect System.get_env # starts the listener :fs.start_link(:watcher, Path.absname("web/static")) :fs.subscribe(:watcher) loop end def loop do receive do {_watcher_process, {:fs, :file_event}, {path, flags}} -> # logs events to the screen IO.puts("* #{path} -> #{Enum.join flags, ", "}") end loop end end Watcher.start

Start the server again, change some file inside web/static and enjoy the results.

# save app.js * <app_path>/web/static/js/app.js -> inodemetamod, modified # save a new file * <app_path>/web/static/js/app2.js -> created, modified, finderinfomod, xattrmod # delete a file * <app_path>/web/static/js/app2.js -> renamed

That’s it for now, next time we’ll talk about long running processes.