Front-end packages with Phoenix and Brunch
How to manage front-end assets, including their images or fonts, with a default Phoenix asset management stack?
In my recent Rails asset pipeline vs Phoenix and Brunch article, I've stated that there isn't a single uniform way in Brunch for pulling images, fonts or anything not JS/CSS from NPM yet. I've also presented the possible workarounds: using copy plugin like copycat-brunch or relying on Bower support. Personally, I didn't like either of those, as Bower is basically dying and using plugins for such a simple task as copying files around seemed like an overkill to me.
That's why in my another article, Installing Font Awesome from NPM in Phoenix, I've presented a way to do that using nothing but native Brunch configuration. The solution was based on
conventions.assets option and it seemed simple and elegant to me at the time. However, @iwarshak pointed out to me that it doesn't work anymore due to a recent update in Brunch. And indeed, by updating Brunch to latest v2.8.2, I've confirmed that pulling assets from node_modules directory using
conventions.assets option doesn't work. So I've decided to reiterate.
Brunch of solutions
So how do you manage those damn static assets?
Once again, I've researched a huge related Brunch issue (this post was most helpful), Stack Overflow (this thread) and the NPM blog (this entry). It appears there's really no generic approach here and it highly depends on a project and sometimes also on a specific front-end package. So I've compiled a final list of options, each with an use case.
It seems that Webpack is overall a more popular solution for managing assets than Brunch. And it definitely is more flexible. That's because Webpack was created with a different goal than Brunch. While Brunch aims to run on conventions and minimize the configuration required (even when plugins are involved), Webpack is just a bucket of highly configurable building blocks. I like conventions and I like to stick with the defaults if possible, so I'll stick to Brunch for now, but Webpack is certainly an interesting and more bulletproof option.
Phoenix makes it trivial to configure Webpack, as explained in this article or in official docs. The former article even shows a way of configuring Webpack for compatibility with existing Phoenix and Brunch structure. So you are really free to choose the tool that fits your needs best.
Use Brunch copy plugin
You can go with a route most resembling the Webpack experience and handle this case with a Brunch plugin specialized in copying files. There are a few options: copycat-brunch, copyfilemon-brunch and assetsmanager-brunch. The first one, copycat-brunch, seemed to be the most mature and feature-rich to me, so I've used it and I can confirm it works OK.
The only thing I don't like about those plugins is that they watch and copy files and write console reports separately from the main Brunch logic which is also capable of doing all those things. Therefore, it's not 100% integrated and a bit overkill. But that's perhaps just me being pedantic.
Use Brunch command-line plugin
If you prefer to use command-line for attending your assets, there's the after-brunch plugin for running arbitrary commands after build. That's OK if you need to not just copy, but for example process images with
convert command. Otherwise, I'd stick with copy plugin instead.
It is possible to create symlinks in web/static/assets to appropriate files in node_modules. These symlinks will be properly followed by Brunch and they will properly get into Git. You just have to make sure they're relative links. Here's how you could include Font Awesome from NPM, after installing it via
npm install --save font-awesome:
lang:bash mkdir web/static/assets/fonts cd web/static/assets/fonts ln -s ../../../../node_modules/font-awesome/fonts/fontawesome-webfont.* .
As you can see, creating relative symlinks is not so pleasant. Also, you'll have to look into the filesystem in order to see what you're linking to in node_modules. I've also discovered that broken links won't make
brunch build command fail, so you won't get any early feedback if some dependency was not resolved or has changed its file structure. Therefore, I'd recommend the next solution over this one.
Copy files manually
This is the most basic solution there is. Pick needed files, either from node_modules or just straight from GitHub or anywhere else, and put them where needed. You loose version control, you pollute your repo and you cut yourself from easy updates. But it works. And it may actually be better than symlink solution as you won't risk silently deploying broken assets whose symlinks became dead in this way or another and Brunch didn't care to fail because of that.
NPM lifecycle hook
It was suggested on the NPM blog that until final solution emerges to manage front-end package dependencies, you could write custom script and run it as NPM lifecycle hook after
npm install. But I don't think it's a good choice with Brunch. You should keep your dependencies organized in a single, obvious place and I'd definitely consider brunch-config.js more so than a custom, randomly placed script that you'll have to look for in package.json. Also, writing custom copy logic in a world with Webpack and Brunch? I don't think so.
Lastly, you could use Bower support built into Brunch. I'd definitely recommend against that. It's poorly documented in Brunch docs and Bower is a dying ecosystem, so you'll be better off sticking to NPM as package source. Therefore, in my opinion, only Bower die-hard fanatics and those who already have large projects based on in should go this route.
Bonus: other concerns
Regardless of how you'll make your front-end assets land in
priv/static, there are two general issues that I've stumbled upon and I'd like you to be aware of. First is related to Phoenix and the other to NPM.
- By default, Phoenix endpoint in your app is configured to serve only specific directories and files via the
Plug.Staticplug. In my case, the default whitelist was
~w(css fonts images js favicon.ico robots.txt). So if some of added files are not being served by your Phoenix server, take a look at your endpoint.ex first. I wouldn't recommend using this setting to kill the Phoenix conventions, though.
- Remember that each NPM package pulls its dependencies privately. But in browser, these dependencies are usually global. So if you'll have two front-end packages that depend on jQuery and each depends on different version, you'll have to decide for yourself which of them to pick to the final browser bundle. Let's hope that new browser features like the Shadow DOM will someday rectify this problem.
Brunch universe is not so shiny when it comes to managing front-end packages. But neither is any other. Not yet. Regardless if it's Rails asset pipeline, Webpack or Brunch you'll have to find an option that's best for your case. That is, until the NPM community finally comes up with a proper uniform browser-aware front-end asset management solution.
Until then, you either have to do things on your own (either by creating symlinks or copying files) or to use plugin mechanics with per-package configuration (copycat-brunch or copy-webpack-plugin). I really hope we'll have something better than that soon.