The main Rails project of Neighborly has today more than 4 years old. Many people in the team invested a good amount of time trying to get the best front end using Rails’ conventions.
The framework advises you pick between two different ways of using external libraries:
vendor/assets
.Honestly though, manually keeping dependencies updated sucks. If you are not doing it through GitHub, you are updating a RubyGem. Yet, vendorized versions aren’t usually expected to have their dependencies loaded without modules.
There is already a community solving these problems, so why would we struggle to keep in the Rails way? Fitting into them takes time, so have to consider if it’s worth the periodical headache.
One way of reaching that goal is with a better separation between Rails and JavaScript. Preferably, without the burden of creating and maintaining distinct applications.
Sprockets -> webpack
Framework-agnostic. Doesn’t matter if you’re using Ember, React or plain JavaScript. It works.
RubyGems -> NPM
For being JavaScript only, receives more focus from JS community. Most often updated compared with RubyGems.
ECMAScript 5 -> ECMAScript 6
Provides modularization and feels like CoffeeScript. Out of the box.
Server side rendering of React components
Embrace Virtual DOM and web components with no penalty for clients.
Sprockets ❤️
It’s still responsible for the final minification and caching.
Rails ❤️
app/assets
is still used to keep all the assets.
Heroku ❤️
git push heroku master
handles the whole deployment.
We are going to turn your app/assets
into an NPM package, having its own dependencies and setup. Make sure you have NPM installed before starting.
$ npm init
$ npm install webpack -g
$ npm install babel-loader expose-loader webpack-dev-server —save-dev
Remember to tell your colleagues about getting webpack binary, by running its install command (and after, installing NPM):
bin/setup
...
system "npm install webpack -g"
Also, to ease first installs — and further deployment — you can tell NPM to pack the current version of the assets every time you call npm install.
app/assets/package.json
{
...
"scripts": {
"postinstall": "webpack —config webpack.config.js"
}
}
By npm installing you will also see a new app/assets/node_modules folder created.
webpack will own your front end environment. It’s responsible for transforming your raw JavaScript files — React, ES6… — into *.js, making sure libraries are also included, adding some flavor to your days. You can actually start enjoying doing front end again!
Since everything will be packed before getting into Rails, just require webpack’s bundle in the application.js file.
app/assets/javascripts/application.js
//= require webpack.bundle.js
For webpack, we can choose Babel as the first addition to our stack. From now on, we can write JavaScript using ECMAScript 6.
app/assets/webpack.config.js
module.exports = {
context: __dirname + '/javascripts',
entry: './webpack_application.js',
output: {
filename: 'webpack.bundle.js',
path: __dirname + '/javascripts',
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}
]
},
resolve: {
extensions: ['', '.js', '.jsx'],
modulesDirectories: ["node_modules", "javascripts"]
},
};
app/assets/javascripts/webpack_application.js
// From here it's with you. This is your new "application.js"
But wait! Don’t forget to gitignore files downloaded and generated by webpack.
.gitignore
...
app/assets/node_modules/
app/assets/javascripts/webpack.bundle.js
Nothing so new here, just provision the machine with Node.js and cache front end libraries located in the app/assets/node_modules..
.travis.yml
language: ruby
node_js: 0.10
cache:
bundler: true
directories:
- app/assets/node_modules
before_script: ./bin/setup
script: bundle exec rspec —colour —format d
If you plan to use React, you will have to make some additions to the setup. First of all, add it as a dependency.
$ npm install react —save
Later, we have to configure webpack and make Babel understand and listen for changes also in *.jsx files. For that, change lines referring to babel-loader and resolved extensions.
app/assets/webpack.config.js
...
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'}
...
extensions: ['', '.js', '.jsx'],
...
I would also highly recommend checking the project react-rails. This way, your server will be able to provide valid HTML of the components even when the client has no JavaScript active.
Tell react-rails the location of your components file and everything should work already. If doesn’t, it’s probably because you are using libraries with problems on running outside a full browser (nowadays, “flash-detect” is an example).
config/application.rb
...
config.react.component_filenames = ['webpack.bundle.js']
...
Another process will be added to your development workflow. If you use foreman to manage a Procfile, you can add webpack to a development version of it. This will monitor your assets folder for any changes and, after noticing any, will compile just the difference from the latest output files.
$ gem install foreman
Procfile.development
...
webpack: webpack -wc —config app/assets/webpack.config.js
Start foreman and open the browser. Development environment set up.
$ foreman start -f Procfile.development
One of the better things about Heroku is its git push
/done workflow. You don’t need to have a local machine with the whole development environment working, just git push heroku master
and the project is built in the production server. Our deployment doesn’t break this.
The idea is that we have Heroku detecting our application as containing two distinct apps: a Node.js and a Ruby on Rails. First, the Node.js one, to install front end dependencies and use webpack to build files readable by Sprockets. Later, Rails takes over expecting to find specific files in app/assets
and there’s a happy ending.
.buildpacks
https://github.com/heroku/heroku-buildpack-nodejs
https://github.com/heroku/heroku-buildpack-ruby
In the terminal:
$ heroku config:set BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git
Here is a little trick: since Node.js’ Heroku buildpack expects to find the package.json in the root path, a node_modules with dependencies would also be created in the root. Since we are willing to respect Rails’ conventions and keep assets inside app/assets, we created a package.json ease the access to the one in the right folder.
package.json
{
"name": "frontend-deployment",
"version": "0.0.0",
"private": true,
"engines": {
"node": ">= 0.10"
},
"scripts": {
"postinstall": "cd app/assets && npm install"
},
"author": "Neighborly Corporation"
}
From now on, you have freedom to create your own structure inside app/assets, respecting the environment existent before. webpack_application.js is the only connection between back end and JavaScript, allowing a better separation of tools and code.