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