Adding Webpack 3 to Phoenix

Whether or not your motivation for adding Webpack to Phoenix has to do with React, I’ve found Webpacker project template to be one of the easiest ways to get started. You can clone the repo as such or hand pick the interesting bits as we do in the following.

Step 1: New Phoenix Project

$ mix phx.new my_app

Step 2: Copy Webpacker Files

Remove brunch-config.js and copy files from Webpacker GitHub repo. At the time of writing, the files and folders to copy are:

assets/js/components/
assets/stylus/
assets/.babelrc
assets/postcss.config.js
assets/webpack.config.js

Example commit

Step 3: Edit package.json

Replace Brunch related scripts with these:

"scripts": {
"start": "npm run watch",
"watch": "MIX_ENV=dev webpack-dev-server --inline --hot --stdin --colors --public localhost:8080",
"deploy": "MIX_ENV=prod webpack -p"
},

Example commit

Step 4: Install Libraries

$ cd assets
$ yarn remove babel-brunch brunch clean-css-brunch uglify-js-brunch
$ yarn add -D babel-cli babel-core babel-loader babel-preset-es2015
$ yarn add -D babel-preset-es2016 babel-preset-react babel-preset-react-hmre
$ yarn add -D copy-webpack-plugin css-loader extract-text-webpack-plugin
$ yarn add -D file-loader image-webpack-loader postcss postcss-loader
$ yarn add -D react react-dom react-hot-loader style-loader stylus
$ yarn add -D stylus-loader url-loader webpack webpack-dev-server
$ yarn deploy

Alternatively, you can use npm uninstall, npm i -D and npm run deploy.

Step 5: Configure Phoenix

Edit watchers in dev.exs:

watchers: [
node: [
"node_modules/.bin/webpack-dev-server", "--inline", "--colors", "--hot",
"--stdin", "--host", "localhost", "--port", "8080", "--public",
"localhost:8080",
cd: Path.expand("../assets", __DIR__)
]
]

Add helpers to layout_view.ex:

def js_script_tag do
if Mix.env == :prod do
~s(<script src="/js/app.js"></script>)
else
~s(<script src="http://localhost:8080/js/app.js"></script>)
end
end
def css_link_tag do
if Mix.env == :prod do
~s(<link rel="stylesheet" type="text/css" href="/css/app.css" media="screen,projection" />)
else
""
end
end

Inside app.html.eex, replace CSS link with <%= {:safe, css_link_tag() } %> and JavaScript link with <%= {:safe, js_script_tag() } %>.

If you like, you can add the example React component to index.html.eex and verify rendering with mix phx.server.

Example commit


That’s it. You’re now officially using Webpack with Hot Module Replacement (HMR). The rest of the steps are purely optional.

Step 6: (Optional) Edit Path Helpers

If you need separate JS & CSS for admin panel, here’s how to remove hard-coded file names from your views.

Replace helpers in layout_view.ex with these:

def webpack_js_path(conn, path) do
if Mix.env == :prod do
static_path(conn, path)
else
"//localhost:8080#{path}"
end
end
def webpack_css_path(conn, path) do
if Mix.env == :prod do
static_path(conn, path)
else
""
end
end

Inside app.html.eex, replace CSS link with

<link rel="stylesheet" href="<%= webpack_css_path(@conn, "/css/app.css") %>">

and JavaScript link with

<script src="<%= webpack_js_path(@conn, "/js/app.js") %>"></script>

Once again, you can verify rendering with mix phx.server.

Example commit

Step 7: (Optional) Replace Stylus with Sass

$ cd assets
$ yarn remove stylus stylus-loader
$ yarn add -D node-sass sass-loader

Delete folder assets/stylus, rename app.css to app.scss and finally replace Stylus entries inside webpack.config.js as follows:

stylus/app.styl   >    css/app.scss
(css|styl) > (css|scss)
stylus-loader > sass-loader
.styl > .scss

Example commit

Step 8: (Optional) Remove React

Delete folder assets/js/components, remove React related lines from app.js and type

$ cd assets
$ yarn remove react react-dom react-hot-loader

Example commit