Setting up a Phoenix application with Webpack 2 and Bootstrap 4

I recently started an application in Phoenix/ Elixir after hearing an awesome talk by Jose Valim at Abstractions.io this past year. I love the ecosystem and the phoenix.new generator, but since I am most familiar with Webpack, I wanted to use its build process for my front end assets. After looking around for some bootstrap tooling for theming, I stumbled across bootstrap-loader which allows you to compile bootstrap with different variables and write SASS using the bootstrap mixins. This leads to a nice auto-reloading development environment where we can easily test out new bootstrap themes, and modify them to our needs.

Here is the link to the Github repository for this post

Creating a Phoenix App

Phoenix Install Instructions
Up and Running Guide

Once you have elixir and the phoenix mix archives installed, you can create a new project by running: mix phoenix.new myproject. This will create a boilerplate starter application with brunch installed by default (don’t worry, it’s super easy to replace). Next, cd myproject and run mix ecto.create. This will create a database for our application and assumes Postgres is installed with a postgres superuser. To change the defaults, you can modify config/dev.exs.

Replacing Brunch with Webpack

Much of this section was taken from: http://matthewlehner.net/using-webpack-with-phoenix-and-elixir/. This section will be a little different as I will be using yarn, webpack 2.

For handling front end dependencies, I will be using yarn. Why yarn over npm? It is an order of magnitude faster installing npm modules and it generates a yarn.lock file which locks your dependencies (and their dependencies) to specific versions, rather than using npm’s ^ minor version update scheme.

First, lets get rid of the brunch configuration, rm brunch-config.js. Let’s add webpack with yarn add — dev webpack.

To handle javascript assets like brunch was, we should also install babel to convert the ES6 javascript to ES5. https://github.com/babel/babel-loader yarn add — dev babel-loader babel-core babel-preset-env.

We also need a loader to copy our static assets to the output directory: yarn add — dev copy-webpack-plugin. We can also delete the brunch dependencies in our package.json so that it looks like:

Now let’s make a webpack.config.js:

Our webpack config now handles copying everything from the assets folder and compiling our javascript for us. You can test it out by running rm -rf priv/static/* and yarn deploy and verifying that we have assets in: priv/static/. Now, we need to tell phoenix about our new build process. To do this, open up config/dev.exs and replace the watcher section to look like:

This will run webpack’s watch target when we start our server, so our changes will be compiled and dumped to our static asset directory. Let’s test it out by running mix phoenix.server. If you visit localhost:4000, you’ll see the correct html output, but app.css, and app.js are missing. That is because we are compiling our assets to a different directory. Lets change our web/templates/layout/app.html.eex to look like:

Notice I commented out the app.css for now. We’ll get to that in the bootstrap-loader section. With that change, we still get an error trying to connect to http://localhost:4000/dist/app.js. To remedy this, we just need to add an entry for the priv/static/dist folder in lib/myproject/endpoint.ex:

plug Plug.Static,
at: “/”, from: :myproject, gzip: false,
only: ~w(dist css fonts images js favicon.ico robots.txt)

Now, our javascript is loading. Let’s modify web/static/js/app.js to actually do something and verify we have some nice ES6 features.

const project = ‘Myproject’;
setTimeout(() => {
console.log(`Hello from ${project}!`);
}, 1000);

Back at localhost:4000, we should now see the console output: Hello from Myproject!. Awesome, we now have javascript compiling and running correctly, now to fix that ugly page.

Adding a highly customizable bootstrap

Just as a note, this section was written with the bootstrap version v4.0.0-alpha.6 and bootstrap-loader v2.0.0-beta.22. The install instructions for bootstrap v3 and v4 are included in the bootstrap-loader docs, but we will be using v4-alpha because it has flexbox support and because we enjoy hating ourselves for being flavor of the month loving front end developers. You can follow along with the install instructions for 4 there, but at the time of this writing, they support v4.0.0-alpha.4 and we’ll be making some config changes to get alpha.6 working.

So! Lets add bootstrap-loader and all of the dependencies it needs and a few more: yarn add — dev bootstrap@v4.0.0-alpha.6 bootstrap-loader css-loader node-sass resolve-url-loader sass-loader style-loader url-loader imports-loader exports-loader postcss postcss-loader postcss-flexbugs-fixes autoprefixer jquery tether extract-text-webpack-plugin.

To set up the bootstrap-loader, we need to provide a .bootstraprc file:

Notice we specified a few files in this config block. Let’s create those now:

rm -rf ./web/static/css/*
touch ./web/static/css/pre-bootstrap-customizations.scss
touch ./web/static/css/bootstrap-customizations.scss
touch ./web/static/css/app.scss

Also, we went a little off course from the bootstrap-loader documentation with the styleLoader config:

styleLoaders:
— style-loader
— css-loader?-autoprefixer
— resolve-url-loader?sourceMap
— postcss-loader?sourceMap
— sass-loader?sourceMap

The -autoprefixer option to the css-loader tells it to not autoprefix any of our compiled css (which it does by default). Instead, we added the postcss-loader which can do a whoooole lot of fun things. What it does is provides a plugin system for transforming styles using javascript. You can use it to autoprefix things, or scope styles to inline react components. For our purposes, we’ll use it to autoprefix our output css, and fix up some pesky flexbox issues. Let’s make a postcss.config.js file:

This configuration was stolen from bootstraps autoprefixer config: https://github.com/twbs/bootstrap/blob/v4-dev/grunt/postcss.config.js

Now, change the webpack.config.js to use bootstrap loader:

A lot went on there. The most important parts to note are:

  • We added a new entry-point, which tells webpack to include bootstrap-loader in our entry point.
  • We added a ProvidePlugin which provides the bootstrap libraries to our javascript as globals. Bootstrap 4 requires jquery and tether as dependencies, so we attach those to global scope.
  • Finally, we added some loaders for assets that bootstrap cares about.

Don’t worry, the self-loathing from webpack configuration hell will pass soon.

Let’s try everything out now with mix phoenix.server.

Change up our web/templates/layout/app.html.eex to include the new css:

<link rel=”stylesheet” href=”<%= static_path(@conn, “/dist/app.css”) %>”>

And viola! We now have our app running with our customized bootstrap configurations! It still looks pretty bad beacuse of the v3 to v4 changes, so let’s fix that up.

Replace the body web/templates/layout/app.html.eex with:

I made a utility function show_flash so the bootstrap notifications don’t show when there is no message body. Modify web/web.ex to give us the get_flash/1 function:

and add the following to web/views/layout_view.ex:

Since the alerts are clickable, we should initialize the click handlers for them in web/static/js/app.js:

$(“.alert”).alert();

Now, when we reload http://localhost:4000/, we should see our lovely new template in all of its autoprefixed-webpacked glory.

Customizing Bootstrap

Now that we have a working setup that could have been accomplished by adding a couple CDN tags, you may be asking yourself why? Let’s take a look at what we can do. The coolest part about this setup is the ability to modify the bootstrap variables before they are compiled. Let’s do that by modifying web/static/css/pre-bootstrap-customizations.scss:

$brand-primary: yellow !default;

The page will auto-reload and your retinas will have the pleasure of being burned out by our nice new theme (You can try #03A9F4 for a nice experience).

You can see the full list of variable overrides here: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss

For some pre-baked themes, you can go to the Bootswatch v4 page and under the Download dropdown for each theme, you can copy the _variables.scss file and place it in your pre-customizations file.

Another cool feature is that your app.scss now runs in the context of bootstrap so you can use bootstrap’s media query mixins

@include media-breakpoint-down(sm) {
.jumbotron {
display: none;
}
}

Now the jumbotron won’t display on mobile!

This post is just touching the surface of what you can do with this configuration. This is now my ideal bootstrap setup and I can’t wait for bootstrap v4 to become stable so everyone can embrace the new flexbox