Updated instructions for Phoenix 1.1.4 and custom Webpack dev server
Generate your new app without brunch:
$ mix phoenix.new my_app --no-brunch
$ cd my_app
This is file that we want to be able to compile, web/static/js/index.js
:
// Phoenix' dependencies
import "../../../deps/phoenix/priv/static/phoenix";
import "../../../deps/phoenix_html/priv/static/phoenix_html";
// Shiny new, hot React component
import React, { Component } from "react";
import { render } from "react-dom";
class Root extends Component {
render() {
return <h1>omg so hot</h1>;
}
}
render(<Root />, document.getElementById("root"));
Install the required js dependencies using npm
:
$ echo '{"private": true}' > package.json
$ npm install --save babel-core babel-polyfill babel-loader babel-preset-es2015 babel-preset-react react react-dom webpack
$ npm install --save-dev webpack-dev-middleware webpack-hot-middleware express cors babel-preset-react-hmre babel-preset-stage-0 babel-preset-es2015
A lot of stuff, right?
We need a webpack config. Here’s webpack.config.js
:
var path = require("path");
var webpack = require("webpack");
var publicPath = "http://localhost:4001/";
var env = process.env.MIX_ENV || "dev";
var prod = env === "prod";
var entry = "./web/static/js/index.js";
var hot = "webpack-hot-middleware/client?path=" + publicPath + "__webpack_hmr";
var plugins = [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
__PROD: prod,
__DEV: env === "dev",
}),
];
if (env === "dev") {
plugins.push(new webpack.HotModuleReplacementPlugin());
}
module.exports = {
devtool: prod ? null : "cheap-module-eval-source-map",
entry: prod ? entry : [hot, entry],
output: {
path: path.resolve(__dirname) + "/priv/static/js",
filename: "index.bundle.js",
publicPath: publicPath,
},
plugins: plugins,
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ["babel"],
exclude: path.resolve(__dirname, "node_modules"),
},
],
},
};
And we need a node server for development, webpack.dev.js
:
#!/usr/bin/env node
var express = require("express");
var webpack = require("webpack");
var config = require("./webpack.config");
var compiler = webpack(config);
var app = express();
app.use(require("cors")());
app.use(
require("webpack-dev-middleware")(compiler, {
noInfo: true,
publicPath: config.output.publicPath,
}),
);
app.use(
require("webpack-hot-middleware")(compiler, {
log: console.log,
}),
);
app.listen(4001, "localhost", function (err) {
if (err) return console.error(err);
console.log("dev server running on localhost:4001");
});
// Exit on end of STDIN
process.stdin.resume();
process.stdin.on("end", function () {
process.exit(0);
});
Allow it to be executed:
$ chmod +x webpack.dev.js
Babel 6 needs a .babelrc
so let’s add it:
{
"presets": ["es2015", "react", "stage-0"],
"env": {
"development": {
"presets": ["react-hmre"]
}
}
}
Make your app run that script as a watcher in dev
. config/dev.exs
:
config :speaker, MyApp.Endpoint,
http: [port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false,
watchers: [{Path.expand("webpack.dev.js"), []}]
# ...
config :speaker, MyApp.Endpoint,
live_reload: [
patterns: [
# ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r{priv/gettext/.*(po)$},
~r{web/views/.*(ex)$},
~r{web/templates/.*(eex)$}
]
]
And finally add this to the bottom of web/templates/layout/app.html.eex
:
<%= if Mix.env == :dev do %>
<script src='http://localhost:4001/index.bundle.js'></script>
<% else %>
<script src="<%= static_path(@conn, "/js/index.bundle.js") %>"></script>
<% end %>
And web/templates/page/index.html.eex
is just:
<div id="root"></div>
Building for production
Add lib/mix/tasks/digest.ex
:
defmodule Mix.Tasks.MyApp.Digest do
use Mix.Task
def run(args) do
Mix.Shell.IO.cmd "NODE_ENV=production ./node_modules/webpack/bin/webpack.js -p"
:ok = Mix.Tasks.Phoenix.Digest.run(args)
end
end
And in mix.exs
:
defmodule MyApp.Mixfile do
# ...
def project do
[ # ...
aliases: ["phoenix.digest": "my_app.digest"]]
end
# ...
end