מדריך: יצירת אפליקציית React בתוך שרת Rails 7
זה מרגיש כאילו רק אתמול פרסמתי כאן מדריך איך לעבוד עם React ב Rails 6 והנה יצאה ריילס 7 עם גישה חדשה לגמרי לעבודה עם JavaScript. בפוסט זה נראה את הג'ם vite-ruby שמאפשר לנו לבנות אפליקציית ריאקט עם ריילס 7 גם בלי webpacker.
השינוי הגדול המרכזי של Rails 7 ביחס לעבודה עם JavaScript הוא ההחלטה לוותר על שלב ה Precompilation. הטענה של DHH היא שהטכנולוגיות בשלות: עם HTTP Push אפשר להחליף את ה Bundling בלי לאבד ביצועים, ודפדפנים יודעים כבר להריץ JavaScript ברמה טובה כך שלא צריך Babel. נכון, אנחנו מפסידים את scss אבל אולי זה לא סוף העולם.
הקורבן הגדול ביותר של ההחלטה הזאת הוא דווקא JSX, כי בלי pre-compilation אין מי שיהפוך את ה JSX-ים לקוד JavaScript רגיל. לפני כמה ימים כתבתי על ספריית htm והיא יום אחד תהיה המפתח לחיבור. הבעיה שכרגע העבודה עם jspm ובאופן כללי עם import maps לא מספיק יציבה - הרבה מודולים ב jspm ייכשלו אם ננסה להוריד אותם אלינו, ואחרים ייכשלו בכל מקרה. כן אפשר לתקן כל תקלה, אבל אם אתם רוצים שהכלים יעבדו בשבילכם ולא אתם בשביל הכלים אני ממליץ בינתיים לחכות עם import maps.
מצד שני הג'ם webpacker נמצא ממש בסוף דרכו המקצועית ולכן נצטרך דרך אחרת לשלב בין קוד ריילס לקוד פרונט אנד. הדרך הזאת היא הג'ם vite-rails. התוכנית שלנו לפוסט:
נבנה אפליקציית ריילס ואפליקציית ריאקט שמחוברת אליה באמצעות הג'ם vite-rails.
נעביר משתנים מריילס לריאקט באמצעות הג'ם gon.
נשתמש בריאקט ראוטר כדי לטעון את הדף המתאים באפליקציית הריאקט.
מוכנים? בואו נצא לדרך.
1. הקמת אפליקציית ריילס חדשה
קודם כל אני מוודא שריילס 7 מותקן לי על המחשב עם:
$ rails --version
Rails 7.0.3
יוצר אפליקציה חדשה:
$ rails new HelloReactWorld1
נכנס לתיקיה:
$ cd HelloReactWorld1
בגלל שאני צריך שכל ה Controllers באפליקציה יגיעו לריאקט, אני מעדיף לעדכן את ה Layout שיתאים לי לריאקט ולא להשתמש בשכבת ה Views של ריילס. אני מעדכן את הקובץ app/views/layouts/application.html.erb
וכותב בו את התוכן הבא:
<!DOCTYPE html>
<html>
<head>
<title>HelloReactWorld1</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<div id="app"></div>
</body>
</html>
עכשיו הגיע הזמן ליצור שני קונטרולרים בשביל שני דפים באתר. משורת הפקודה אני מריץ:
$ rails g controller home index
$ rails g controller about index
ונעדכן את הקובץ config/routes.rb
לתוכן הבא:
Rails.application.routes.draw do
root to: 'home#index'
get 'about/index'
end
2. הוספת אפליקציית צד לקוח עם vite
משורת הפקודה מתקינים את הג'ם:
$ bundle add vite_rails
מייצרים את הסטארטרים עם:
$ bundle exec vite install
ועכשיו צריך להתקין את התמיכה בריאקט. מעדכנים את הקובץ vite.config.ts
שנוצר בתיקיה הראשית של הפרויקט לתוכן הבא:
import { defineConfig } from 'vite';
import RubyPlugin from 'vite-plugin-ruby';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
RubyPlugin(),
react(),
],
})
מפעילים משורת הפקודה בתיקיה הראשית של הפרויקט את הפקודה:
$ npm install --save @vitejs/plugin-react react react-dom react-router-dom@6 react-refresh
ואנחנו כמעט מוכנים. נקודת הכניסה שלנו למערכת היא הקובץ app/javascript/entrypoints/application.js
, אבל אני הייתי מעדיף שהוא יהיה עם הסיומת jsx
כדי שאוכל לכתוב בו קוד jsx. לכן נשנה לו את השם ל application.jsx
:
$ mv app/javascript/entrypoints/application.js app/javascript/entrypoints/application.jsx
ובמקביל בקובץ app/views/layouts/application.html.erb
אני מחליף את השורה שמתחילה ב vite_javascript_tag
ובמקומה כותב:
<%= vite_javascript_tag 'application.jsx' %>
אבל זה לא הכל - בעבודה עם ריאקט יש פלאגין שנקרא react refresh שחייבים לטעון מתוך ה HTML. ויט היה מעדכן את ה HTML אם היה יכול, אבל אנחנו מגישים את ה HTML דרך ריילס ולכן צריך להוסיף את העדכון ידנית לקובץ ה layout. אחרי העדכון הקובץ app/views/layouts/application.html.erb
נראה כך:
<!DOCTYPE html>
<html>
<head>
<title>HelloReactWorld1</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<script type="module">
import RefreshRuntime from "/vite-dev/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<%= vite_client_tag %>
</head>
<body>
<div id="app"></div>
<%= vite_javascript_tag 'application.jsx' %>
</body>
</html>
3. פיתוח קוד צד לקוח
נמשיך לכתיבת קוד ריאקט שיורכב מדף בית, דף אודות וקובץ ניתוב. בשביל דף הבית אני יוצר תיקיה חדשה:
$ mkdir app/javascript/routes
ובתוכה יוצר את הקובץ app/javascript/routes/homepage.jsx
עם התוכן הבא:
import React, { useState } from 'react';
import { Link } from "react-router-dom";
export default function Homepage() {
const [name, setName] = useState('');
return (
<div>
<label>
Please type your name:
<input type="text" value={name} onChange={(e) => setName(e.currentTarget.value)} />
</label>
{name !== '' && <p>Hello! {name}</p>}
<Link to="/about/index">About Us</Link>
</div>
);
}
ובקובץ app/javaascript/entrypoints/application.jsx
אני מוחק את כל התוכן ובמקומו כותב את התוכן הבא:
import React from "react";
import { createRoot } from 'react-dom/client';
import Homepage from "../routes/homepage";
import About from '../routes/about';
import {
BrowserRouter as Router,
Route,
Routes,
} from "react-router-dom";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/about/index" element={<About />} />
</Routes>
</Router>
);
}
const root = createRoot(document.querySelector('#app'));
root.render(<App />);
הקובץ השלישי של היישום נקרא app/javascript/routes/about.jsx
ובו אני כותב את התוכן הבא:
import React from 'react';
import { Link } from "react-router-dom";
export default function About() {
return (
<div>
<p>About Us</p>
<Link to="/">Back Home</Link>
</div>
);
}
4. הרצה ובדיקה
אנחנו מוכנים לצאת לדרך. נפעיל בחלון נפרד את הפקודה:
$ ./bin/vite dev
ובחלון נוסף את הפקודה:
$ ./bin/rails s
ועכשיו אפשר להיכנס מדפדפן ל localhost:3000
ולראות את דף הריאקט הראשון שיצרנו, ללחוץ על הקישור ולהגיע לדף השני.