React and CoffeeScript (cjsx) on Middleman

Login to Access Code

Middleman and React are an incredibly simple and awesome stack. The alternative you will often see is with npm/browserify/gulp which I personally find to be more convoluted than ruby/gems. All I want to do is be able to separate and organize react components, be able to write with coffeescript and have them compile for production. These three requirements took me an hour or more to properly setup and configure with gulp/browserify but only a few minutes with middleman.

Start off with a vanilla Middleman project

If you’re going to deploy to heroku, you should probably start with my post on deploying middleman to heroku as it is very short and is a good test to make sure you have things setup properly as a prerequisite.

Add in the React and Coffee React Gems

To get react loading through sprockets as well as support for coffeescript (cjsx) you just need to drop these two gems in your Gemfile and then bundle install.

gem 'middleman-react'
gem 'sprockets-coffee-react'

Include React Assets

You have the gems, so lets put them to work. We need to tell sprockets to load, process and compile our cjsx templates:

require 'sprockets/coffee-react'

::Sprockets.register_preprocessor 'application/javascript', ::Sprockets::CoffeeReact
::Sprockets.register_engine '.cjsx', ::Sprockets::CoffeeReactScript
::Sprockets.register_engine '.js.cjsx', ::Sprockets::CoffeeReactScript

activate :react

after_configuration do
  sprockets.append_path File.dirname(::React::Source.bundled_path_for('react.js'))
end

Organize the React Components

Organization matters greatly to me. Let’s create some folders before we start building out our application. First I want to create a components folder for my reusable react components and a pages folder which will render my various page-specific scripts:

mkdir source/javascripts/components
mkdir source/javascripts/pages

Creating our first React Component and Rendering

First we need to properly order and require our various assets. I’m doing to delete the source/javascripts/all.js file and instead create an application.js.coffee file that loads in React and our components:

//= require react
//= require react-with-addons
//= require_tree './components'

Now lets make our first React component. I’ll turn the default Middleman markup into a Welcome component and put it in source/javascripts/components/welcome.js.coffee

@Welcome = React.createClass
  render: ->
    <div className="welcome">
      <h1>Middleman is Watching</h1>
      <p className="doc">
        <a href="http://middlemanapp.com/">Read Online Documentation</a>
      </p>
    </div>

For demo purposes, we simply want to render this single component on to a page somewhere. Let’s create our home page script that will do the rendering for us and keep it separate from the rest of the application. Under source/javascripts/pages/home.js.coffee we simply need to tell React what the most parent component is and where to render it:

React.render <Welcome />, document.getElementById('home')

Setting up our Page

Now we need to update our layout and templating. I’m going to change the default javascript file loaded (from above where I changed “all” to “application”) and I want each .html page to be able to define it’s own React page script so that they are pseudo-namespaced:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">

    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">

    <title><%= current_page.data.title || "The Middleman" %></title>

    <%= stylesheet_link_tag "normalize", "all" %>

    <!-- Updated to 'application' instead of 'all' -->
    <%= javascript_include_tag  "application" %>
  </head>

  <body class="<%= page_classes %>">
    <%= yield %>

    <!-- Include any scripts defined on the rendered page -->
    <% if data.page.scripts %>
      <%= javascript_include_tag data.page.scripts %>
    <% end %>
  </body>
</html>

Notice how I render the included page scripts in the layout file if they are defined in the individual html files. Notice the div#home included on this page only. If I later want to make this a single page application, my components and rendering will still be namespaced in a way that avoids collisions:

---
title: Welcome to Middleman
scripts: pages/home
---
<div id="home"></div>

Up and running

If you didn’t already have Middleman running, start it up now and go checkout the page:

middleman s