A tale of build tools

I recently started with Phoenix and tried to get on board with the whole brunch thing, but that faded quickly enough. I’m more back-end focused and don’t want to learn some obscure front-end thing I won’t use elsewhere. So, I decided to switch out brunch with webpack.

I’m using webpack 1 still, since it’s easier for me at the moment, but will switch to webpack 2 sometime in the future. Unless of course The Next Big Thing TM for front-end comes out before then.

Steps

Like most things Phoenix, this was actually pretty easy to switch out. I’m going to show you my personal setup I use on my own projects. I am not claiming for this to be The One True Way or anything remotely close to that.

First, let’s add some npm dependencies to package.json. This is what I use:

{
    "babel-core": "^6.24.0",
    "babel-loader": "^6.4.1",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-es2015": "^6.24.0",
    "copy-webpack-plugin": "^4.0.1",
    "cross-env": "^3.2.4",
    "css-loader": "^0.27.3",
    "eslint": "^3.18.0",
    "eslint-loader": "^1.6.3",
    "extract-text-webpack-plugin": "^1.0.1",
    "json-loader": "^0.5.4",
    "node-sass": "^4.5.0",
    "sass-loader": "^6.0.3",
    "style-loader": "^0.14.1",
    "webpack": "1"
}

Add a .babelrc file:


{
  "presets": [
    "es2015"
  ],
  "plugins": [
    "transform-runtime"
  ]
}

Add an .eslintrc file (this is a snippet version of my rules, see full file here):

{
  "env": {
    "browser": true,
    "node": true
  },

  "parserOptions": {
    "sourceType": "module"
  },

  "ecmaFeatures": {
    "arrowFunctions": true,
    "destructuring": true,
    "classes": true,
    "defaultParams": true,
    "blockBindings": true,
    "modules": true,
    "objectLiteralComputedProperties": true,
    "objectLiteralShorthandMethods": true,
    "objectLiteralShorthandProperties": true,
    "restParams": true,
    "spread": true,
    "templateStrings": true
  },

  "rules": {
    "accessor-pairs": 2,
    "array-bracket-spacing": 0,
    "block-scoped-var": 0,
    "brace-style": [2, "1tbs", { "allowSingleLine": true }],
    "camelcase": 0,
    "etc ..."
  }
}

And then let’s add a webpack.config.js file:

var ExtractTextPlugin = require("extract-text-webpack-plugin");
var CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  entry: ["./web/static/css/app.scss", "./web/static/js/app.js"],

  output: {
    path: "./priv/static",
    filename: "js/app.js"
  },

  resolve: {
    modulesDirectories: [ "node_modules", __dirname + "/web/static/js" ]
  },

  devtool: "source-map",

  module: {
    loaders: [{
      test: /\.json$/,
      loader: 'json'
    },{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel!eslint"
    }, {
      test: /\.scss$/,
      loader: ExtractTextPlugin.extract(
        "style",
        "css!sass?sourceMap"
      )
    }]
  },

  plugins: [
    new CopyWebpackPlugin([{ from: "./web/static/assets" }]),
    new ExtractTextPlugin("css/app.css")
  ]
};

After that we will tweak package.json scripts, change this:

"scripts": {
  "deploy": "brunch build --production",
  "watch": "brunch watch --stdin"
},

to this:

"scripts": {
  "deploy": "webpack -p",
  "watch": "webpack --watch-stdin --progress --color"
},

And finally, update your Phoenix config, config/dev.exs. Change:

watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
           cd: Path.expand("../", __DIR__)]]

to

watchers: [npm: ["run", "watch"]]

Conclusion

That wasn’t so bad! Now when you run npm watch or mix phoenix.server, you will be using webpack instead of brunch.

Going further

If you use Vue.js, then I’ve added a post Add Vue.js to your Phoenix project’s webpack 1 config