Adding React to a Middleman project with Webpack
Middleman is a static site generator written in Ruby. It's a great way to
produce rich static content sites without the need for a server. Despite
this, it doesn't come with internal support for modern JavaScript
frameworks like React. Luckily, Middleman 4 ships with a feature called
External Pipeline
which allows wiring in your own external build tool like Gulp or Webpack.
Let's look at how to integrate Webpack with Middleman for the purpose of
using React on our site.
Install development dependencies
First, let's install the dependencies we'll need to build our React code.
Note that we're using the --save-dev
flag to indicate to npm that we
should append these libraries to the devDependencies
section of our
package.json
.
$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react webpack uglifyjs-webpack-plugin
-
babel-core
is the core Babel package for transpiling ES6 and JSX into
browser-friendly ES5 JavaScript.
-
babel-loader
is a Webpack loader which loads files from our path into
Babel.
-
babel-preset-es2015
is a preset for Babel to transpile ES6 code.
-
babel-preset-react
is a preset for Babel to transpile JSX code.
-
webpack
is Webpack itself.
-
uglifyjs-webpack-plugin
is a Webpack plugin to uglify and compress our
code for production.
Install React
Next, install the React packages, this time using --save
to indicate
these are runtime dependencies:
$ npm install --save react react-dom
Set up your Babel configuration file
Babel has its own configuration file inside .babelrc
. Create this file
in the root of your Middleman project with the following contents:
{
"presets": [
"es2015", "react"
]
}
This file tells Babel to use the es2015
and react
presets we installed
in the first step.
Configure Webpack
Next we'll configure Webpack with a basic configuration file that supports
both development and production environments. Place the following inside
the file webpack.config.js
in the root of your project:
// webpack.config.js
var webpack = require('webpack');
const isProduction = process.env.NODE_ENV === 'production';
const productionPlugins = [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
isProduction ? new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
}) : null,
];
module.exports = {
entry: './assets/javascripts/index.js',
devtool: isProduction ? false : 'source-map',
output: {
library: 'MyApp',
path: __dirname + '/tmp/dist',
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: isProduction ? productionPlugins : []
};
Notice that we use the isProduction
flag to toggle the use of the
UglifyJsPlugin
as well as whether we use a devtool
to provide us with
source maps for debugging.
Configure Middleman to build Webpack
Now that we've configured Webpack, let's tell Middleman to execute it
whenever it rebuilds. To do this, we'll activate the external pipeline
plugin in our config.rb
and point it at the Webpack executable:
# config.rb
activate :external_pipeline,
name: :webpack,
command: build? ?
"NODE_ENV=production ./node_modules/webpack/bin/webpack.js --bail -p" :
"./node_modules/webpack/bin/webpack.js --watch -d --progress --color",
source: "tmp/dist",
latency: 1
When we run middleman build
, we run Webpack with NODE_ENV=production
.
This triggers our production build options in our Webpack configuration.
When using middleman server
though, we tell Webpack to --watch
for
changes and automatically rebuild.
Middleman will look for the result of our built assets inside tmp/dist
.
Let's go ahead and create that directory now:
Build an example React component
Now that we've got our tooling configured, let's create a simple React
component to test that everything works. First, create a directory to
store your Webpack JavaScript assets. I place these files in
assets/javascripts
since Sprockets looks inside source/javascripts
,
and I don't want Sprockets to attempt to build my React code.
$ mkdir -p assets/javascripts
Next, let's sketch out a React component. Note that from index.js
I'm
exporting a function renderHello
which renders the HelloWorld
component to a DOM element specified by ID. This allows us to call upon
fragments of React code from within existing pages.
// assets/javascripts/index.js
import React from 'react';
import ReactDOM from 'react-dom';
const HelloReact = props => (
<div>Hello, React!</div>
);
function renderHello(id) {
const el = document.getElementById(selector);
ReactDOM.render(<HelloReact />, el);
}
export default {
renderHello
}
Mount the component
Finally, mount the component onto an existing DOM element on a page.
First include the built bundle.js
file. Then, call renderHello
:
<body>
<div id="hello">
</div>
<%= javascript_include_tag "bundle" %>
<script type="text/javascript">
MyApp.default.renderHello('hello');
</script>
</body>
Other resources