Prerequisites for local setup:
- rails 7 installed
- yarn installed
Create a new rails app by the command:
rails new esbuildApp -j esbuild
Now let us look at the Package.json
and Procfile.dev
Package.json
package.json
has our js dependencies and scripts
"dependencies": {
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.1.3",
"esbuild": "^0.15.7"
},
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"
}
The esbuild command here basically just imports the stuff from app/javascript/*
& runs a bundle command, a source map command and then it throws it all inside
of app/assets/builds
with the public path of assets.
This is important because inside of app/assets/builds
you have application.js
and application.js.map
basically spits everything out here then the rails asset pipeline uses it.
Procfile.dev
If you only start your application with a bundle exec rails s
it's not going to run the yarn build --watch
The yarn build
command inside of our package.json
is the command which delivers our javascript into the asset pipeline.
The --watch
flag in the command watches for changes when you update your application code for hot reloading.
We can run your Procfile.dev
by the command:
bin/dev
which uses the foreman gem
to run both of the below processes in two pids.
Firstly it runs the yarn command to throw everything into the source map. Then it starts the rails server.
Now let us stop the server with control+c.
Let's set up a home page and add some javascript to it
To set up a home page using the rails generator run the command:
rails g controller pages home
Now let's update the routes.rb
file, to point the localhost:3000/
request to pages/home
Rails.application.routes.draw do
root 'pages#home'
end
To make javascript appear on the screen, let's add a javascript controller
using stimulus generator and run the command:
rails g stimulus react
so this creates our app/javascript_controllers/react_controller.js
Now let's update the react_controller.js
by adding a console.log
to test the javascript changes reflect on our app's root page.
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="react"
export default class extends Controller {
connect() {
console.log("React controller connected");
}
}
Now update the home view to inject the react controller javascript code. Go to app/views/pages/home.html.erb
and make the below changes:
<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
<%= content_tag(:div, "", id:"app", data: {
controller: "react"
}) %>
We are using a content_tag
. In the content tag we have to tell it what type of content by providing the below info:
- type:
:div
, - id:
app
- Which stimulus controller to use. In our case:
app/javascript/controllers/react_controller.js
. We don't need the_controller
part so we just typecontroller: "react"
Restart the server and run the command: bin/dev
you should now be able to see the log: "React controller connected"
in the browser console.
Now let us stop the server with control+c
.
Now to add some actual react components.
Firstly let's add react & react-dom
packages with the command:
yarn add react react-dom
Now check the changes in Package.json
"dependencies": {
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.1.3",
"esbuild": "^0.15.7",
"react": "^18.2.0", // this is new
"react-dom": "^18.2.0" // this is new
},
Create App.jsx
inside app/javascript/components/
folder by running the commands:
mkdir app/javascript/components/
touch app/javascript/components/App.jsx
Let us create a basic counter in the App.jsx
with a button to increment the count state and a <p>
tag displaying it:
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times!</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default App;
Now let us import the App.jsx
into the react_controller.js
and render it onto the app
element using the createRoot
from react-dom/client
import { Controller } from "@hotwired/stimulus";
import React from "react";
import { createRoot } from "react-dom/client";
import App from "../components/App";
// Connects to data-controller="react"
export default class extends Controller {
connect() {
console.log("React controller connected");
const app = document.getElementById("app");
createRoot(app).render(<App />);
}
}
In the console we are getting an error:
Now to fix the esbuild loader error by updating the package.json
build script to:
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets --loader:.js=jsx"
}