React in Hostile Environments

JavaScript Finds A Way

What do I mean by hostile?

Hostile Environments

• established codebases that are resistant to change

• disparate ecosystems

• opinionated frameworks

• existing team workflow and knowledge-base

You're bringing a new creature into an established ecosystem.

And if you're not careful, it'll go Jurassic Park on you...

Our example framework

the undisputed king in the Ruby world

ReactJS

a small powerful library for managing UI

This doesn't have to go well...

How does Rails handle assets?

• Asset Pipeline (Sprockets)

• searches `/assets` directory for JS and stylesheets

• concatenates all JS into a single file

• concatenates styles into a single file

• optimizes for both dev and prod environments

Ruby gems can be added for additional handling

• Assets are added to the main layout for all pages

Pros and Cons of Sprockets

√   Up & running quickly

√   Don't have to think about handling assets

√   Defaults are mostly what you want

 

X   Estranged from npm and node modules 😢

X   Many gems for asset handling aren't well-maintained

X   Customizing asset compilation can be challenging

Why add React to Rails?

• better architecture for handling UI complexity

• improves frontend testability

• refactor and rewrite as we go

Roads not Taken

React-Rails gem

√    Up & running is really easy

√    Asset Pipeline (no compiler setup needed)

√    Babel gem: ES6 is free

√    Server-side rendering is super easy

X   Still divorced from npm and node modules

X   Testing is a nightmare 😱

Roads not Taken

React_on_Rails gem

√   Uses npm and webpack! yay!

√   Calling components is easy

√   Server-side rendering comes bundled in

X   The gem is really large with a lot baked in

A Successful Integration

√   Smooth development workflow

     • calling components and passing data easily

       • asset compilation should be simple

√   Access to webpack, npm, and helpful modules

√   Testability

√   Minimalism / Transparency

√   Deployment should be seamless

How do we start?

  $ npm init

  $ touch webpack.config.js

var webpack = require('webpack');
var path = require('path');

module.exports = {
  entry: [ './client/'],
  module: {
    loaders: [{ test: /\.js?$/, loader: 'babel', exclude: /node_modules/ }]
  },
  resolve: {
    extensions: ['', '.js']
  },
  output: {
    path: path.join(__dirname, '/app/assets/webpack/'),
    publicPath: '/',
    filename: 'bundle.js'
  },
  plugins: []
};

Connecting to the Pipeline

  $ mkdir client/; mkdir client/components

  $ atom app/views/layouts/application.html.erb

  ...  
  </body>
  <%= javascript_include_tag "bundle" %>
</html>

Calling Components

  $ touch app/helpers/react_helper.rb

# app/helpers/react_helper.rb
require 'json'

module ReactHelper
  def react_component(component_name, props = {})
    content_tag(
      :div,
      nil,
      class: 'react-component-target',
      data: {
        react_class: component_name,
        react_props: props.to_json
      }
    )
  end
end
<!-- calling a component in the view -->
<%= react_component 'SomeComponent', text: 'Hello, world!' %>

Connecting Components

  $ touch client/index.js

// client/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import SomeComponent from './components/SomeComponent';

const components = { SomeComponent };

const reactTargetDivs = document.getElementsByClassName('react-component-target');

let componentName;
let componentProps;

Array.prototype.forEach.call(reactTargetDivs, (targetDiv) => {
  componentName = targetDiv.getAttribute('data-react-class');
  componentProps = JSON.parse(targetDiv.getAttribute('data-react-props'));
  ReactDOM.render(
    React.createElement(
       components[componentName],
       componentProps
     ),
    targetDiv
  );
});

Creating a Component

  $ touch client/components/SomeComponent.js

// client/components/SomeComponent.js
import React from 'react';

const SomeComponent = (props) => {
  return (
    <h1>{props.text}</h1>
  );
};

export default SomeComponent;

Improving Dev Workflow

  $ gem install foreman

  $ touch Procfile

  $ foreman start

# Procfile
webpack: npm run watch
rails: rails server
// package.json
    
...
"scripts": {
  "watch": "webpack --watch --colors --progress"
},

Seamless Deployment

  $ touch lib/tasks/assets.rake

# lib/tasks/assets.rake

namespace :assets do
  task :precompile => :webpack
end

task :webpack do
  sh "npm install"
  sh "./node_modules/.bin/webpack"
end

How Well Did it Work?

√   Overall fairly well

     • we were able to find an integration that worked well with our workflow

       • we can refactor our app in pieces as needed

 !  Drawbacks

    • server-side rendering

React-Webpack-Rails gem

a simple install tool for this setup

• largely unremarkable

• does the initial setup so you don't miss a step

• works well for established Rails apps or brand new

 

$ rails g react:webpack:install

Questions?

Alan Smith

twitter.com/@_alanbsmith

github.com/alanbsmith

React in Hostile Environments

By Alan Smith

React in Hostile Environments

  • 503