Donāt you hate refreshing your browser every time you change a bit of code?
This is a lesson from The Complete React on Rails Course, which helps Ruby on Rails developers become amazing at building UIs in Rails using React.js. Itās only meant for people who are serious about using React in production, so donāt click this link unless youāre one ofĀ them.
In this lesson, weāre going to look at how to use hot asset reloading in a react_on_rails app with Hot Module Replacement.
The app weāre building is a calendar appointments app. You can find the code on Github:
GitHub - learnetto/calreact-hmr_Contribute to calreact-hmr development by creating an account on GitHub._github.com
Hot reloading means automatically showing live code changes in the browser without having to reload the page every time you change something.
Itās quite useful in development. You can have your code and browser windows next to each other and simply write code and save it, and see the changes immediately without switching over to the browser or refreshing it.
The react_on_rails gem uses a special type of hot reloading called Hot Module Replacement or HMR via Webpack Dev server to provide the hot assets to Rails, rather than the asset pipeline.
When you start the app, Webpack builds the bundle and then continues to watch the source files for changes. If it detects a source file change, it rebuilds only the changed module(s) and updates them in the browser through an HMR runtime.
The react_on_rails gem has a README on how to set this up.
BUT the example code they provide has a lot of extra bits and itās easy to get lost in npm hell trying to do all of that at once.
So I made this super simplified step-by-step tutorial on how to do it.š
I recommend you clone the react_on_rails repo anyway on your computer and look in the spec/dummy directory for the hot reloading setup code example.
You have to change a number of files and settings, so Iām going to take you through the setup step by step.
You can do hot reloading for any assets including Javascript, CSS and images but in this lesson weāre only going to use it for Javascript.
Hot reloading is only meant for development, so we need to produce two sets of config filesāāāone for development to use hot reloading and one for production (to use static assets).
Letās first do the setup for hot reloading and weāll add the extra setup for serving static files in production, later.
1. Weāre first going to install some javascript packages weāll need.
The two key packages are react-transform-hmr (which enables reloading through a hot module replacement API) and webpack-dev-server (the development server that serves the live assets).
In addition, we need a babel plugin for applying the transform as well.
We need to add these to our client/package.json file:
ābabel-plugin-react-transformā: ā^2.0.2ā,āreact-transform-hmrā: ā^1.0.4ā,āwebpack-dev-serverā: ā^1.16.2ā
Weāre also going to add jquery and jquery-ujs here so that they are bundled by webpack (instead of Rails) and made available to any other packages that depends on them.
ājqueryā: ā^3.1.1ā,ājquery-ujsā: ā^1.2.2ā,
Then letās run:
$ npm install
And thatās the packages done.
2. Next, letās set up the webpack config files.
We need a base config which will be shared by the hot config and the static one.
Iāve copied these config files from the react_on_rails repo spec/dummy/client directory into my app and simplified them to just use the basics that we need.
Letās take a quick look at them.
The base config mainly sets up the entry points and file extensions to be resolved. It leaves the output setting for the environment-specific config files.
// Common client-side webpack configuration used by// webpack.client.rails.hot.config and webpack.client.rails.build.config.
const webpack = require('webpack');const path = require('path');
const devBuild = process.env.NODE_ENV !== 'production';const nodeEnv = devBuild ? 'development' : 'production';
module.exports = {
// the project dircontext: __dirname,entry: ['babel-polyfill','es5-shim/es5-shim','es5-shim/es5-sham','jquery-ujs','jquery','./app/bundles/Appointments/startup/registration',],resolve: {extensions: ['', '.js', '.jsx'],alias: {libs: path.join(process.cwd(), 'app', 'libs'),react: path.resolve('./node_modules/react'),'react-dom': path.resolve('./node_modules/react-dom'),},},
plugins: [new webpack.DefinePlugin({'process.env': {NODE_ENV: JSON.stringify(nodeEnv),},TRACE_TURBOLINKS: devBuild,}),
],module: {loaders: [{ test: require.resolve('jquery'), loader: 'expose?jQuery' },{ test: require.resolve('jquery'), loader: 'expose?$' },],},};
It also exposes jQuery as a global so that itās available to any other packages dependent on it.
Now letās look at the hot config. It builds on top of the base config.
It pulls in the basic config and adds some extra things for hot reloading. It sets the port where webpack dev server will run (3500).
// Run with Rails server like this:// rails s// cd client && babel-node server-rails-hot.js// Note that Foreman (Procfile.dev) has also been configured to take care of this.
const path = require('path');const webpack = require('webpack');
const config = require('./webpack.client.base.config');
const hotRailsPort = process.env.HOT_RAILS_PORT || 3500;
It adds a couple of entry pointsāāāthe first is the live asset from webpack dev server and the second is a module it needs.
config.entry.push(`webpack-dev-server/client?http://localhost:${hotRailsPort}`,'webpack/hot/only-dev-server');
Want this lesson on video? Click on the imageĀ above
Then it sets the output filename and path. Weāre just calling it webpack-bundle.
config.output = {filename: 'webpack-bundle.js',path: path.join(__dirname, 'public'),publicPath: `http://localhost:${hotRailsPort}/`,};
If you look in the react_on_rails repo example, they split it into two filesāāāone for the app and one for vendor files.
But weāre keeping it simple here and just creating one output file.
The most important bit here is this loader which takes our jsx files and applies the hmr transform through a babel plugin.
config.module.loaders.push({test: /\.jsx?$/,loader: 'babel',exclude: /node_modules/,query: {plugins: [['react-transform',{transforms: [{transform: 'react-transform-hmr',imports: ['react'],locals: ['module'],},],},],],},},{test: require.resolve('jquery-ujs'),loader: 'imports?jQuery=jquery',});
And then we add the hmr plugin to the config and export it.
config.plugins.push(new webpack.HotModuleReplacementPlugin(),new webpack.NoErrorsPlugin());
config.devtool = 'eval-source-map';
console.log('Webpack HOT dev build for Rails');
module.exports = config;
The Complete React on Rails 5 Course_Max Rose-Collins, Co-founder and Ruby Engineer at RankTracker You know Rails well. You've been using it for a while, atā¦_learnetto.com
3. The next step is to include the correct webpack assets file for each environment in our Rails application layout file, based on whether weāre using hot reloading or not.
Weāll use a view helper to configure the correct assets to loadāāāeither the āhotā assets or the āstaticā assets depending on the environment.
<!DOCTYPE html><html><head><title>Calreact</title><%= csrf_meta_tags %>
<%= stylesheet\_link\_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= env\_javascript\_include\_tag(hot: \['[http://localhost:3500/webpack-bundle.js'](http://localhost:3500/webpack-bundle.js%27)\]) %>
<%= env\_javascript\_include\_tag(static: 'application\_static',
hot: 'application\_non\_webpack',
'data-turbolinks-track' => true) %>
</head>
<body><%= yield %></body></html>
Here, env_javascript_include_tag is a helper method provided by react_on_rails which includes the hot or static asset file based on whether an environment variable is set to use hot reloading or not.
We have two tags here, one to load the hot assets which donāt use turbolinks and another for static assets.
And then we need to create these two files.
//= require webpack-bundle//= require application_non_webpack
We require the webpack-bundle first and then require application_non_webpack.
Letās create that file nowāāāapplication_non_webpack.js.erb:
<% if ENV["DISABLE_TURBOLINKS"].blank? %><% require_asset "turbolinks" %><% end %>
react_on_rails uses this bit of code to require turbolinks based on an env variable. We can use it to easily disable or enable turbolinks.
Ok so thatās all set up.
Now if we want to use static assets then weāll include just the application_static file which includes webpack_bundle and any non webpack assets.
And if weāre using hot reloading then weāll include the hot assets from webpack dev server and the non webpack assets like turbolinks from application_non_webpack.
4. Next we need to make sure that we include the webpack generated files in our assets initializer config file.
type = ENV["REACT_ON_RAILS_ENV"] == "HOT" ? "non_webpack" : "static"Rails.application.config.assets.precompile +=["application_#{type}.js"]
So again here, based on this env variable we set the name of the file to include in our precompiled assets.
5. Next, we need to create a new Procfile for hot reloading.
Weāll call it Procfile.hot. Remember, it goes in the root of the app directory.
It has a couple of processes
# Procfile for development with hot reloading of JavaScript and CSSrails: REACT_ON_RAILS_ENV=HOT rails s -b 0.0.0.0
# Run the hot reload server for client developmenthot-assets: HOT_RAILS_PORT=3500 npm run hot-assets
First the rails serverāāāit runs it with the REACT_ON_RAILS_ENV
variable set to HOT
.
Second, hot-assets
which is a script for serving the hot assets on port 3500 via webpack dev server.
So thatās Procfile.hot all done.
6. We need to add the server-rails-hot.js script for running the webpack dev server to our client directory.
This file imports webpack, webpackdevserver and our hot config and then creates a new dev server and runs it on port 3500.
import webpack from 'webpack';import WebpackDevServer from 'webpack-dev-server';
import webpackConfig from './webpack.client.rails.hot.config';
const hotRailsPort = process.env.HOT_RAILS_PORT || 3500;
const compiler = webpack(webpackConfig);
const devServer = new WebpackDevServer(compiler, {contentBase: `http://lvh.me:${hotRailsPort}`,publicPath: webpackConfig.output.publicPath,hot: true,inline: true,historyApiFallback: true,quiet: false,noInfo: false,lazy: false,stats: {colors: true,hash: false,version: false,chunks: false,children: false,},});
devServer.listen(hotRailsPort, 'localhost', err => {if (err) console.error(err);console.log(`=> š„ Webpack development server is running on port ${hotRailsPort}`);});
This file needs to be used by an npm script.And thatās the last missing piece of the puzzle.
7. Letās add some scripts in the two package.json files for npm to run our hot reloading code
First, letās do the package in the root directory.
"scripts": {"postinstall": "cd ./client && npm install","build:clean": "rm -r app/assets/webpack/* || true","build:dev:client": "(cd client && npm run build:dev:client --silent)","hot-assets": "(cd client && npm run hot-assets)"}
The build:dev:client script is for static assets which weāll look at in a minute.
But the hot-assets one is what we need for hot reloading.
It cdās into the client directory and npm runs another script called hot-assets.
So letās add that into the client package file now:
"scripts": {"build:test": "npm run build:client && npm run build:server","build:client": "webpack --config webpack.client.rails.build.config.js","build:dev:client": "webpack -w --config webpack.client.rails.build.config.js","hot-assets": "babel-node server-rails-hot.js"},
So here you can see the hot-assets script runs babel-node (a command line tool provided by babel) with our server-rails-hot.js file that we set up earlier.
And thatās everything we need to set up to get hot reloading of our javascript working.
So letās see it in action now!
Letās go to the terminal and fire up foreman with our new Procfile.hot:
$ foreman start -f Procfile.hot
foreman start -f Procfile.hot
You can see Webpack development server is running on port 3500
and our bundle gets compiled successfully.
Now letās test our app in the browser.
To test hot reloading, Iām going to place my code editor next to the browser and youāll see the live changes I make automatically appear in the browser.
Changes made to React components and utilities code appear immediately in the browserĀ š„
If we look at the logs, weāll see that webpack dev server is serving these hot updates.
Look at the size of the update files. They are a few bytes. So this is quite fast.
Alright, so thatās hot reloading set up for development.
8. Now one final thing is to set up the config for serving static assets to use in production or if we donāt want to use live reloading in development.
We need a couple of thingsāāāa static webpack config and a static Procfile.
Letās add the webpack config first:
// Run like this:// cd client && npm run build:client// Note that Foreman (Procfile.dev) has also been configured to take care of this.
const webpack = require('webpack');
const config = require('./webpack.client.base.config');
const devBuild = process.env.NODE_ENV !== 'production';
config.output = {filename: 'webpack-bundle.js',path: '../app/assets/webpack',publicPath: '/assets/',};
// See webpack.client.base.config for adding modules common to both the webpack dev server and rails
config.module.loaders.push({test: /\.jsx?$/,loader: 'babel-loader',exclude: /node_modules/,},{test: require.resolve('react'),loader: 'imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham',},{test: require.resolve('jquery-ujs'),loader: 'imports?jQuery=jquery',});
config.plugins.push(new webpack.optimize.DedupePlugin());
if (devBuild) {console.log('Webpack dev build for Rails');config.devtool = 'eval-source-map';} else {console.log('Webpack production build for Rails');}
module.exports = config;
Iāve used the webpack.client.rails.build.config.js file from the react_on_rails spec/dummy example and simplified it to only include the bits we need.
Similar to the hot webpack config, it builds upon the base config. It sets the output filename and adds the loaders we need.
Now letās add a Procfile:
# Run Rails without hot reloading (static assets).rails: REACT_ON_RAILS_ENV= rails s -b 0.0.0.0
# Build client assets, watching for changes.rails-client-assets: sh -c 'npm run build:dev:client'
# Build server assets, watching for changes. Remove if not server rendering.#rails-server-assets: sh -c 'npm run build:dev:server'
We need the top two processes, one for rails and the other for serving the assets.
The last process is for server rendering, which Iāve commented out.
The rails-client-assets script runs build:dev:client
which we defined in package.json earlier like this:
"build:dev:client": "webpack -w --config webpack.client.rails.build.config.js",
It runs webpack with the build config file we just created.
Ok, so thatās the setup for production.
We can test it by running foreman with Procfile.static:
$ foreman start -f Procfile.static
Weāll no longer get the webpack dev server message in the logs and weāll just have the rails app running on port 5000.
Now if we make a change in a component, it wonāt appear automatically in the browser. Weāll need to reload the page manually to see the change.
So thatās hot reloading of javascript assets using Hot Module Replacement!
š š If you found this tutorial useful, please clickity click the green heart.šš
Oh and donāt forget to check out my course which has a full detailed video of this lesson and over 3 hours of more lessons which will take you from zero to hero using React with Rails.
This is a lesson from The Complete React on Rails Course, which helps Ruby on Rails developers become amazing at building UIs in Rails using React.js. Itās only meant for people who are serious about using React in production, so donāt click this link unless youāre one ofĀ them.
The Complete React on Rails 5 Course_Max Rose-Collins, Co-founder and Ruby Engineer at RankTracker You know Rails well. You've been using it for a while, atā¦_learnetto.com