How to plug in webpack 2.0 into phoenix 1.3.0-rc

It’s also about how well the technologies mesh together (as far as I’m concerned, not very well).

Last May I performed a little experiment, as a learning experience: generate a stock 1.2 Phoenix project - and simply replace Brunch with Webpack 2 with the original stock behaviour and look restored - which resulted in lots of WTF moments.

By default the filename of the resulting file is the MD5 hash of the file’s contents with the original extension of the required resource.

You can configure a custom filename template for your file using the query parameter name. For instance, to copy a file from your context directory into the output directory retaining the full directory structure, you might use ?name=[path][name].[ext].

Keeping the original filename requires a custom action :101: - so to keep those

<%= static_path(@conn, "/res/resource.rs") %>

references working in the EEx templates, you need to take active control of the file names in the Webpack configuration. On the flip side if you want to take advantage of the hashed file names there will need to be another step in the toolchain that updates those file references in the EEx templates.

  • Previously this was enough in js/app.js:
    .

    import “phoenix_html”;

Now I need all this

import appStyles from '../css/app.css';
import phoenixStyles from '../css/phoenix.css';
import phoenixImg from '../images/phoenix.png';
import faviconIco from '../favicon.ico';
import robotTxt from '../robots.txt';

import "phoenix_html";

for the sole purpose of communicating to Webpack that these files are “part of the package” - some of them are already sitting there, ready to go in the source directory and are being referenced in the markup where they are relevant - and just because I may have used the correct spelling in the JavaScript doesn’t mean I did the same in the markup.

  • Webpack complains that there are fonts missing

So apparently css/app.css is a Bootstrap v3.3.5 file which references the glyphicons-halflings-regular font. But the stock installation doesn’t even provide it - making it necessary to grab it somewhere else and retro-fit it in order to shut Webpack up.

Now the stock brunch-config.js looks something like this:

exports.config = {
  sourceMaps: false,
  production: true,

  modules: {
    definition: false,
    // The wrapper for browsers in a way that:
    //
    // 1. Phoenix.Socket, Phoenix.Channel and so on are available
    // 2. the exports variable does not leak
    // 3. the Socket, Channel variables and so on do not leak
    wrapper: function(path, code){
      return "(function(exports){\n" + code + "\n})(typeof(exports) === \"undefined\" ? window.Phoenix = window.Phoenix || {} : exports);\n";
    }
  },

  files: {
    javascripts: {
      joinTo: 'phoenix.js'
    },
  },

  // Phoenix paths configuration
  paths: {
    // Which directories to watch
    watched: ["web/static", "test/static"],

    // Where to compile files to
    public: "priv/static"
  },

  // Configure your plugins
  plugins: {
    babel: {
      // Do not use ES6 compiler in vendor code
      ignore: [/^(web\/static\/vendor)/]
    }
  }
};

Nothing too terribly terrifying if something has to be changed. Now to emulate the effect of the Brunch configuration with Webpack 2, I ended up with this unwieldy looking thing:

const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");

const extractCSS = new ExtractTextPlugin({ filename: 'css/[name].css', allChunks: true });

module.exports = {
  context:  path.resolve(__dirname, "./web/static"),
  entry: {
    app: "./js/app.js"
  },
  output: {
    filename: "js/[name].js",
    path: path.resolve(__dirname, "./priv/static")
  },
  resolve: {
    modules: ["node_modules"],
    extensions: [".js", ".json", ".css"],
    alias: {
      "phoenix": path.resolve(__dirname, "./deps/phoenix/priv/static/phoenix.js"),
      "phoenix_html": path.resolve(__dirname, "./deps/phoenix_html/priv/static/phoenix_html.js")
    }
  },
  module: {
    rules: [
      { // for css/app.css
        test: /\.css$/,
        use: extractCSS.extract({ fallback: "style-loader", use: "css-loader" })
      },
      { // for image/phoenix.png
        test: /\.(jpe?g|png|gif|svg|ico|txt)$/i,
        use: 'file-loader?name=[path][name].[ext]'
      },
      { // Needed for bootstrap 3 fonts - retrofitted into web/static/font for phoenix.css
        test: /\.(woff|woff2|ttf|svg|eot)/,
        loader: 'url-loader',
        options: {
          limit: 10000
        }
      }
    ]
  },
  plugins: [ extractCSS ]
}

(I think at this point I gave up trying to get the font file names in non-hashed forms into static/font).

Finally if the development setup requires that the Webpack dev server is installed and up -and-running we really haven’t reached a “Brunch equivalent state” yet where the pages are being served by the Phoenix Server rather than the Webpack dev server.

I found it amusing that while the vue-cli “webpack-simple” template uses the Webpack dev server, the fully fledged “webpack” template does not - it instead elects to build an express-based server around the Webpack dev middleware - I can only assume that somebody decided that the dev server wasn’t providing adequate presentation fidelity.

There are lots of good reasons to adopt Webpack if you are using React or Vue.js - you just have to commit to actually learn and adopt the Webpack way. But I have yet to see a convincing argument that it is worth tolerating the burden of maintaining a Webpack configuration for an EEx based project which just uses a tried-and-true “sprinkling” of JavaScript.

FYI: Apparently HMR and RHL are in fact “not the same thing”.

1 Like