• בלוג
  • מדריך: יצירת אפליקציית React בתוך שרת Rails 7

מדריך: יצירת אפליקציית React בתוך שרת Rails 7

24/05/2022

זה מרגיש כאילו רק אתמול פרסמתי כאן מדריך איך לעבוד עם 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. התוכנית שלנו לפוסט:

  1. נבנה אפליקציית ריילס ואפליקציית ריאקט שמחוברת אליה באמצעות הג'ם vite-rails.

  2. נעביר משתנים מריילס לריאקט באמצעות הג'ם gon.

  3. נשתמש בריאקט ראוטר כדי לטעון את הדף המתאים באפליקציית הריאקט.

מוכנים? בואו נצא לדרך.

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 ולראות את דף הריאקט הראשון שיצרנו, ללחוץ על הקישור ולהגיע לדף השני.