React+Sass+Typescript with Phoenix framework using Webpack
If you’re playing with Elixir and Phoenix you’ll probably already know that Phoenix uses Brunch.io to build the assets pipeline. I initially started building my app with React / Redux + SASS and I was quite happy, but when I decided to add Typescript to the recipe, I found Brunch.io wasn’t very helpful!
I’ve already used these tools using Webpack as building tool, so I decided to switch to it. I had a working Webpack configuration I was using in other projects, so I only had to find out how to apply it to Phoenix.
When I initially generated the Phoenix app, I didn’t use the --no-brunch
command line flag to generate it without Brunch, and the app itself was alreadyt working. So I wanted to replace the existing Brunch config with the new one without nuking any existing feature.
Below you can find the (few) steps required for this upgrade.
Remove Brunch and install Webpack
To remove Brunch, just remove the assets/brunch-config.js
file and uninstall all brunch-related packages. In my case:
yarn remove brunch babel-brunch clean-css-brunch sass-brunch uglify-js-brunch
In the assets/package.json
file you can also see that Brunch is mentioned in the scripts commands:
"scripts": {
"deploy": "brunch build --production",
"watch": "brunch watch --stdin"
},
Replace them with the Webpack commands (to run the webpack
command you’d probably need to globally install it with yarn global add webpack
):
"scripts": {
"deploy": "webpack -p",
"compile": "webpack --progress --color",
"watch": "webpack --watch-stdin --progress --color"
},
You’re now ready to install the webpack ecosystem we’re going to use:
yarn add -D webpack babel-core babel-loader babel-preset-es2015 copy-webpack-plugin css-loader extract-text-webpack-plugin file-loader node-sass sass-loader style-loader webpack-notifier
Add Typescript to the project
To add Typescript support we need some more packages too:
yarn add -D typescript ts-loader tslint tslint-react @types/phoenix @types/react @types/react-dom @types/react-redux
The Typescript configuration file is assets/tsconfig.json
:
{
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"jsx": "preserve",
"moduleResolution": "node",
"baseUrl": "js",
"outDir": "ts-build",
"allowJs": true
},
"exclude": [
"node_modules",
"priv",
"ts-build"
]
}
As you probably noticed in the previous command, I also installed TSLint support for linting features. Add the related package to your editor (like vscode-tslint for VSCode) to have (almost-)real-time linting warnings.
You also need a configuration file, here’s mytslint.json
file:
{
"extends": ["tslint:recommended", "tslint-react"],
"rules": {
"no-console": [false]
}
}
Webpack configuration
Before configuring Webpack, let’s configure Babel, using the assets/.babelrc
file:
{
"presets": ["es2015", "react"]
}
And now, finally, the big part, the Webpack configuration file, assets/webpack.config.js
:
const env = process.env.NODE_ENV
const path = require("path")
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin")
const config = {
entry: ["./css/app.scss", "./js/app.js"],
output: {
path: path.resolve(__dirname, "../priv/static"),
filename: "js/app.js"
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"],
modules: ["deps", "node_modules"]
},
module: {
rules: [{
test: /\.tsx?$/,
use: ["babel-loader", "ts-loader"]
}, {
test: /\.jsx?$/,
use: "babel-loader"
}, {
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: [{
loader: "css-loader",
options: {
minimize: true,
sourceMap: env === 'production',
},
}, {
loader: "sass-loader",
options: {
includePaths: [path.resolve('node_modules')],
}
}],
fallback: "style-loader"
})
}, {
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
// put fonts in assets/static/fonts/
loader: 'file-loader?name=/fonts/[name].[ext]'
}]
},
plugins: [
new ExtractTextPlugin({
filename: "css/[name].css"
}),
new CopyWebpackPlugin([{ from: "./static" }])
]
};
module.exports = config;
Final polishing and tests
The final step is to update the main template file app.html.eex
to use the generated files (that you can find in the priv/static
folder once compiled):
[..]
<link rel="stylesheet" href="<%= static_path(@conn, "/css/main.css") %>">
[..]
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
The whole pipeline is now ready, except we must tell Phoenix to run Webpack when the server is launched.
At the moment you can already test the pipeline running, from the assets/
folder, the command: yarn run compile
To configure Phoenix, update the config/dev.exs
file replacing Brunch command with Webpack:
watchers: [
node: [
"node_modules/webpack/bin/webpack.js", "--watch-stdin", "--progress", "--color",
cd: Path.expand("../assets", __DIR__)
]
]
That’s it.
Run mix phx.server
and you’ll have both the Phoenix server running and the assets pipeline compiled and watching for file updates.