Nicd

Nicd

MBU - Mix Build Utilities

tl;dr

MBU is a collection of functions and a few macros that I wrote to help in using Mix as a build tool for e.g. frontend building. It supports task dependencies, and watching paths (either with watchers built in to different tools or custom watchers using file_system). You can find it on Hex, documentation on Hexdocs, and code on GitLab.

Motivation

This blog post explains my motivations best (has old code samples but the motivations are the same). The short version is that I was tired of learning different frontend build systems that all seemed to be magical, fragile, or difficult to maintain. I thought about Make and NPM scripts but ended up with my own solution that would serve my own needs: use Mix as a build tool to call the usual Node tools, so that I can write my build scripts in Elixir and know how they work.

If I succeeded in creating anything useful is left up to the reader. :sweat_smile:

How it works

Let’s suppose the following config:

config :mbu,
  auto_paths: true, # Autogenerate output path for tasks
  create_out_paths: true, # Create output path when task runs
  tmp_path: Path.expand(".tmp") # Path under which autogenerated output paths are, i.e. where task output is stored

Then we could have the following build task:

defmodule Mix.Task.Css.Compile do
  use MBU.BuildTask

  @deps ["css.autoprefix", "assets.copy"]

  def in_path(), do: Path.join("assets", "css") |> Path.expand()
  def in_file(), do: Path.join(in_path(), "frontend.scss")

  def args() do
    [
      "--source-map-embed",
      "--output",
      out_path(),
      in_file()
    ]
  end

  task _ do
    exec("node_modules/.bin/node-sass", args()) |> listen()

    out_file = Path.basename(in_file(), "scss") <> "css"
    print_size(Path.join(out_path(), out_file))
  end
end

What this task does is:

  1. Runs the dependencies listed in @deps in parallel before the task is run.
  2. Creates an output directory for the build artifacts based on the task name and :tmp_path, in this case it would be something like /path/to/proj/.tmp/css.compile. This path is returned by out_path/0.
  3. Executes the node-sass command with the given arguments, waits for it to complete and prints the output.
  4. Prints the size of the compiled file using print_size/1.

Now if we wanted to watch the assets/css directory for changes, we could create another task and in that task’s task block, we can do:

watch("FrontendCompileCSS", Mix.Task.Css.Compile.in_path(), Mix.Task.Css.Compile)
|> listen(watch: true)

What this does is it listens to the path given in the second argument for changes, and then runs the task given in the third argument (the first argument is for logging purposes). The last argument could also be an anonymous function to call. The watch: true option makes the listen/2 function also listen for an enter key from the user, which will stop the watching.

This was just a small example, for more realistic usage, see my website’s build tasks at lib/mix/tasks · master · CodeStats / code-stats · GitLab (probably a bit overengineered at the moment as it will have more complex targets in the future).

Pros & cons

I am obviously biased and blind to my own creation but here’s what I think of it:

  • Pros:
    • Minimal features & magic
    • Get to write Elixir, no external deps
    • Easier to fix/debug when build breaks because it’s just small Elixir tasks
  • Cons:
    • Minimal features & magic
    • Verbose especially for small tasks
    • Some tools don’t have command line interfaces to call
    • Needs more testing on Windows

I hope it can be useful to someone or at least can inspire for better Mix helpers in the future (since the code is really not that nice). I use it in my own projects and it seems to work well for me, and now at least I can only blame myself if something goes wrong in the build. If you use it for something, please let me know any feedback you have in this thread. I will also use this thread to notify if I update MBU with new stuff.

Most Liked

Nicd

Nicd

Well, I changed the name to MBU: Mix Build Utilities and put it on hex.pm: mbu | Hex

Hope someone finds it useful for something. :slight_smile:

Nicd

Nicd

First post has been updated with the latest info :slight_smile:

3.0.0 release brings automatic folder handling, so that you don’t need to write your own out_path/0 for tasks that store things in temporary output folders.

christiansakai

christiansakai

I like the concept! I think this is valuable not only for building JS/CSS, but any front end assets in general, such as Elm.

What is the downside of this approach rather than with the basic Brunch configuration that comes with Phoenix?

Where Next?

Popular in Announcing Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29603 241
New
tmbb
PhoenixWS - Websockets over Phoenix Channels Source code on Github here: GitHub - tmbb/phoenix_ws: Websockets implemented over Phoenix Ch...
New
oltarasenko
Dear Elixir community, After a year of development, bug fixes, and improvements, we are proudly ready to share the release of Crawly 0.1...
New
kip
ex_cldr provides localisation and internationalisation support based upon the data from the Unicode CLDR project. Unicode released CLDR ...
407 13014 120
New
RobertDober
Earmark is a pure-Elixir Markdown converter. It is intended to be used as a library (just call Earmark.as_html), but can also be used as...
239 12645 134
New
michalmuskala
Hello everybody. I have just released Jason - a new JSON library. You might be wondering, why do we need a new library? The primary foc...
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
scohen
Lexical Lexical is a next-generation language server for the Elixir programming language. Features Context aware code completion As-you...
New
zoltanszogyenyi
Hey everyone :waving_hand: Excited to join this forum - I am one of the founders and current project maintainers of a popular and open-s...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement