These steps describe creating a Rails/React app, using Shakapacker as the bundler.
Before starting, ensure that you have Yarn installed, for example:
npm i -g yarn
If you'd like easy integration of React with Ruby on Rails, see React on Rails.
The below information applies to a React on Rails app, except that you need to setup some environment variables as follows:
EXECJS_RUNTIME=Node
Create a new Rails app as per the installation instructions in the README.
Add React, as well as the necessary libraries to enable CSS support in your application:
yarn add react react-dom @babel/preset-react
yarn add css-loader style-loader mini-css-extract-plugin css-minimizer-webpack-plugin
Update the Babel configuration in the package.json
file:
"babel": {
"presets": [
"./node_modules/shakapacker/package/babel/preset.js",
+ "@babel/preset-react"
]
},
And that's it. You can now create a React app using app/javascript/application.js
as your entry point.
With HMR enabled, Shakapacker will automatically update only that part of the page that changed when it detects changes in your project files. This has the nice advantage of preserving your app’s state.
To enable HMR in a React app, proceed as follows:
In config/shakapacker.yml
set hmr
is set to true
.
Install the react-refresh package, as well as @pmmmwh/react-refresh-webpack-plugin:
yarn add --dev react-refresh @pmmmwh/react-refresh-webpack-plugin
Alter config/webpack/webpack.config.js
like so:
const { generateWebpackConfig, inliningCss } = require('shakapacker');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
const webpackConfig = generateWebpackConfig();
if (isDevelopment && inliningCss) {
webpackConfig.plugins.push(
new ReactRefreshWebpackPlugin({
overlay: {
sockPort: webpackConfig.devServer.port,
},
})
);
}
module.exports = webpackConfig;
This applies the plugin to the webpack configuration.
Delete the Babel configuration from package.json
:
- "babel": {
- "presets": [
- "./node_modules/shakapacker/package/babel/preset.js",
- "@babel/preset-react"
- ]
- },
Then create a babel.config.js
file in the root of project and add the following:
module.exports = function (api) {
const defaultConfigFunc = require('shakapacker/package/babel/preset.js')
const resultConfig = defaultConfigFunc(api)
const isDevelopmentEnv = api.env('development')
const isProductionEnv = api.env('production')
const isTestEnv = api.env('test')
const changesOnDefault = {
presets: [
[
'@babel/preset-react',
{
development: isDevelopmentEnv || isTestEnv,
useBuiltIns: true
}
]
].filter(Boolean),
plugins: [
isProductionEnv && ['babel-plugin-transform-react-remove-prop-types',
{
removeImport: true
}
],
process.env.WEBPACK_SERVE && 'react-refresh/babel'
].filter(Boolean),
}
resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets]
resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ]
return resultConfig
}
This is taken from the sample React Babel config.
HMR for your React app is now enabled. 🚀
To test that all of the above is working, you can follow these instructions to create a basic React app using Shakapacker.
- Create a new Rails app:
rails new myapp --skip-javascript
cd myapp
bundle add shakapacker --strict
./bin/bundle install
./bin/rails shakapacker:install
yarn add react react-dom @babel/preset-react
yarn add css-loader style-loader mini-css-extract-plugin css-minimizer-webpack-plugin
- Generate a controller
rails g controller site index
echo '<div id="root"></div>' > app/views/site/index.html.erb
- Create a CSS file and a React component:
touch app/javascript/App.css app/javascript/App.js
- Edit
app/javascript/application.js
like so:
import React from 'react';
import { createRoot } from 'react-dom/client';
import HelloMessage from './App';
const container = document.getElementById('root');
const root = createRoot(container);
document.addEventListener('DOMContentLoaded', () => {
root.render(<HelloMessage name="World" />);
});
- Add the following to
app/javascript/App.js
:
import React from 'react';
import 'App.css';
const HelloMessage = ({ name }) => <h1>Hello, {name}!</h1>;
export default HelloMessage;
- Add the following to
app/javascript/App.css
:
h1 { color: blue; }
- Enable HMR in config/shakapacker.yml:
hmr: true
- Install the react-refresh package, as well as @pmmmwh/react-refresh-webpack-plugin:
yarn add --dev react-refresh @pmmmwh/react-refresh-webpack-plugin
- Alter
config/webpack/webpack.config.js
like so:
const { generateWebpackConfig, inliningCss } = require('shakapacker');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
const webpackConfig = generateWebpackConfig();
if (isDevelopment && inliningCss) {
webpackConfig.plugins.push(
new ReactRefreshWebpackPlugin({
overlay: {
sockPort: webpackConfig.devServer.port,
},
})
);
}
module.exports = webpackConfig;
- Remove the Babel configuration from
package.json
- "babel": {
- "presets": [
- "./node_modules/shakapacker/package/babel/preset.js"
- ]
- },
- Create a
babel.config.js
file in the project root and add the following sample code:
module.exports = function (api) {
const defaultConfigFunc = require('shakapacker/package/babel/preset.js')
const resultConfig = defaultConfigFunc(api)
const isDevelopmentEnv = api.env('development')
const isProductionEnv = api.env('production')
const isTestEnv = api.env('test')
const changesOnDefault = {
presets: [
[
'@babel/preset-react',
{
development: isDevelopmentEnv || isTestEnv,
useBuiltIns: true
}
]
].filter(Boolean),
plugins: [
isProductionEnv && ['babel-plugin-transform-react-remove-prop-types',
{
removeImport: true
}
],
process.env.WEBPACK_SERVE && 'react-refresh/babel'
].filter(Boolean),
}
resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets]
resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ]
return resultConfig
}
- Start the Rails server and the
shakapacker-dev-server
in separate console windows:
rails s
./bin/shakapacker-dev-server
-
Edit either the React component at
app/javascript/App.js
or the CSS file atapp/javascript/App.css
and observe the HMR goodness.
Note that HMR will not work if you edit app/javascript/application.js
and experience a full refresh with a warning in the console. For more info on this, see here: pmmmwh/react-refresh-webpack-plugin#177