Build, Deployment ו-Code Review
Build, Deployment, CI/CD ו-Code Review
שיעור כפול לסיום יחידה 3: חצי ראשון — מהקוד ל-Production (Build, Vercel, GitHub Actions). חצי שני — Code Review: מקוד שעובד לקוד מקצועי (Code Smells, Refactoring, הגנה בראיון).
- Build Process: Bundling, Minification, Tree Shaking, Hashing
- Environment Variables: .env, VITE_API_URL — הפרדת סביבות
- Vercel: פריסה ב-3 שלבים + vercel.json ל-SPA Routing
- GitHub Actions: CI/CD — בדיקות אוטומטיות בכל Push
- Lazy Loading + Code Splitting: lazy() + Suspense
- Lighthouse: Performance > 90, Accessibility > 95
- Docker: Multi-stage Build — builder + nginx
- Production Checklist — 10 נקודות לפני הסדנה
- Code Smells: שכפול, פונקציות ארוכות, Magic Numbers
- Refactoring: Extract Function, Meaningful Names, DRY
- הגנה בראיון — לדעת לצדק ולבקר את ארכיטקטורת הפרויקט
Build Process — מה קורה ב-npm run build
בפיתוח, React מריץ Dev Server עם HMR. ב-Production צריך קבצים סטטיים אופטימליים. npm run build מריץ שרשרת שלמה של טרנספורמציות.
| שלב | מה קורה | תוצאה |
|---|---|---|
| Bundling | מאחד את כל הקבצים לחבילות | כמה קבצי JS/CSS גדולים |
| Transpilation | Babel ממיר JSX ו-TypeScript ל-JS | JS שהדפדפן מבין |
| Minification | מסיר רווחים, מקצר שמות | קבצים קטנים ב-70–90% |
| Tree Shaking | מסיר קוד שלא בשימוש | Bundle קטן יותר |
| Hashing | מוסיף hash לשמות הקבצים | Cache Busting אוטומטי |
# הרצת Build
npm run build
# תוצאה — תיקיית dist/:
dist/
index.html
assets/
index-3f4a9b2c.js # JS ראשי + Hash
index-7d8e1f3a.css # CSS + Hash
# Preview לפני פריסה
npm run preview # פותח localhost:4173Environment Variables — הפרדת סביבות
אפליקציה אמיתית מתחברת לכתובות שונות בפיתוח ו-Production. Environment Variables מאפשרים להגדיר פעם אחת ולהשתמש בכל מקום — בלי לשנות קוד.
# .env.development — נטען ב-npm run dev
VITE_API_URL=http://localhost:3000
# .env.production — נטען ב-npm run build
VITE_API_URL=https://api.myapp.com
# .env.local — override מקומי, לא ב-Git! (הוסף ל-.gitignore)
VITE_API_KEY=my-local-key// שימוש בקוד — import.meta.env (Vite)
const apiUrl = import.meta.env.VITE_API_URL;
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 5000,
});
// בדיקת סביבה
if (import.meta.env.DEV) {
console.log('מצב פיתוח — Logs מופעלים');
}! אבטחה קריטית — לעולם לא Secrets ב-Frontend! ›
VITE_API_URL, VITE_APP_NAME — בסדר. כל אחד יכול לראות אותם ב-Browser DevTools → Sources.
VITE_DB_PASSWORD, VITE_API_SECRET_KEY — אסור! הם גלויים לכל גולש, גם אחרי Minification.
Vercel ו-GitHub Actions — Deployment אוטומטי
Vercel הפלטפורמה הפופולרית ביותר לפריסת React Apps. GitHub Actions מאפשר CI/CD — בדיקות ופריסה אוטומטית בכל Push.
פריסה ב-3 שלבים עם Vercel
| שלב | פעולה | תוצאה |
|---|---|---|
| 1. Push לGitHub | git push origin main | קוד מעודכן ב-Repo |
| 2. Vercel מזהה | Webhook מפעיל Build אוטומטי | Build + Tests רצים |
| 3. Deploy | Vercel פורסת ל-CDN גלובלי | https://my-app.vercel.app |
// vercel.json — חובה לReact Router (SPA Routing)!
// ללא זה, Refresh ב-/about יחזיר 404
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}GitHub Actions — CI Workflow
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci # מהיר + דטרמיניסטי
- run: npm test -- --watchAll=false
- run: npm run buildPerformance, Lighthouse ו-Docker
לפני הסדנה — מדידה ואופטימיזציה. Lighthouse מודד ביצועים ונגישות. Code Splitting מקטין זמן טעינה. Docker מאפשר פריסה עקבית בכל סביבה.
Code Splitting + Lazy Loading
import { lazy, Suspense } from 'react';
// במקום: import AdminPage from './AdminPage'; // נטען תמיד
// עם Lazy: נטען רק כשמנווטים לRoute
const AdminPage = lazy(() => import('./AdminPage'));
const SettingsPage = lazy(() => import('./SettingsPage'));
function App() {
return (
<BrowserRouter>
{/* Suspense: מה להציג בזמן הטעינה */}
<Suspense fallback={<div>טוען...</div>}>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/admin' element={<AdminPage />} />
<Route path='/settings' element={<SettingsPage />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}Lighthouse — מדדים לסדנה
| קטגוריה | מה נמדד | יעד |
|---|---|---|
| Performance | FCP, LCP, CLS, TBT | > 90 |
| Accessibility | ARIA, contrast, labels | > 95 |
| Best Practices | HTTPS, Console Errors | > 90 |
| SEO | meta tags, robots.txt | > 90 |
Code Review — מקוד שעובד לקוד מקצועי
קוד שעובד הוא רק ההתחלה. קוד שקשה לקרוא ולשנות הוא חוב טכני (Technical Debt) שמאט פיתוח עתידי. בראיון גאמא — בוחנים יצללו לתוך הקוד שלכם.
Code Smells — ריחות רעים
שכפול קוד (Duplication): הפרת עקרון DRY (Don't Repeat Yourself). אם קוד זהה מופיע בשני מקומות — תקלה אחת תצריך תיקון בשניים.
פונקציות ארוכות (Long Functions): פונקציה שאחראית על יותר מדי דברים. קשה להבין, קשה לבדוק.
מספרי קסם (Magic Numbers): מספרים יתומים בקוד ללא הסבר (86400 — מה זה? שניות ביממה?).
Refactoring — לשפץ מבלי להרוס
// ❌ קוד מסריח — Magic Numbers + שמות לא ברורים
function calc(p, t) {
if (t === 1) return p * 0.9;
if (t === 2) return p * 0.8;
return p;
}
// ✅ קוד נקי — קריא ובר תחזוקה
const USER_TYPES = {
REGULAR: 1,
PREMIUM: 2
};
const DISCOUNTS = {
REGULAR: 0.9, // 10% הנחה
PREMIUM: 0.8 // 20% הנחה
};
function calculateFinalPrice(price, userType) {
if (userType === USER_TYPES.REGULAR) return price * DISCOUNTS.REGULAR;
if (userType === USER_TYPES.PREMIUM) return price * DISCOUNTS.PREMIUM;
return price;
}- כל פונקציה ומחלקה צריכה לעשות דבר אחד — ולעשות אותו טוב
- אם הפונקציה עושה fetch + ולידציה + עדכון UI — פצל לשלוש פונקציות
- פונקציה קצרה = קל לבדוק, קל להבין, קל לתחזק
Production Checklist — לפני הסדנה
לפני שמציגים פרויקט בסדנה — עוברים על Checklist קצר. לחצו על כל פריט כדי לסמן שביצעתם.
אתגר מעשי — פריסה — Env, סודות ו-CI
שיא השיעור: לבנות רכיב עובד עם זווית אבטחה. ההגשה נבדקת אוטומטית.
סיכום — שיעור 22 וסיום יחידה 3
עברנו מ-React עד Production. יחידה 3 הושלמה — אתם מוכנים לבנות ולפרוס פרויקט Fullstack מקצועי.
- Build: Bundling → Transpilation → Minification → Tree Shaking → Hashing
- Environment Variables: VITE_* prefix — Secrets לעולם לא ב-Frontend
- Vercel: GitHub Integration + vercel.json לSPA Routing
- GitHub Actions: npm ci + test + build בכל Push
- Lazy Loading: lazy() + Suspense — Code Splitting לפי Route
- Lighthouse: Performance > 90, Accessibility > 95 — יעדים לסדנה
- Docker Multi-stage: builder (~500MB) + nginx serve (~20MB)
- Code Smells: שכפול, Magic Numbers, פונקציות ארוכות
- Refactoring: שמות ברורים, DRY, Single Responsibility
- ראיון: לדעת לזהות ולבקר את ארכיטקטורת הפרויקט שלך
- פרוס את הפרויקט האישי ל-Vercel — קישור פעיל לסדנה
- הגדר GitHub Actions עם CI — Workflow שמריץ בדיקות בכל Push
- הרץ Lighthouse, תקן בעיות עד ציון > 85 ב-Performance
- יחידה 4: שפת C ו-Assembly — בסיס לכל חולשות האבטחה
שאלות תרגול ותשובות (10)
שאלה 1.1 · מה המטרה של Hash בשם קובץ כמו index-3f4a9b2c.js?
- א. הצפנת תוכן הקובץ לצורכי אבטחה
- ב. ניצחון מטמון
- ג. זיהוי גרסת ספריית React בקובץ
- ד. קיצור שם הקובץ לחיסכון בנפח האחסון
✓ תשובה: ב) — הדפדפן מאחסן קבצים ב-Cache לפי שמם. אם תמיד יהיה index.js, המשתמש לעולם לא יקבל עדכונים. Hash שמשתנה בכל Build מבטיח שהדפדפן יוריד תמיד את הגרסה העדכנית.
שאלה 1.2 · מה ההבדל בין CI ל-CD?
# .env.development — נטען ב-npm run dev VITE_API_URL=http://localhost:3000 # .env.production — נטען ב-npm run build VITE_API_URL=https://api.myapp.com # .env.local — override מקומי, לא ב-Git! (הוסף ל-.gitignore) VITE_API_KEY=my-local-key
- א. CI הוא פריסה ו-CD הוא בדיקות
- ב. CI הוא בדיקות ו-Build אוטומטי בכל Push, ואילו CD הוא פריסה אוטומטית לאחר שה-CI עבר בהצלחה
- ג. שניהם זהים לחלוטין ורק הקיצורים שונים
- ד. CI לפרויקטים גדולים ו-CD לפרויקטים קטנים
✓ תשובה: ב) — Continuous Integration = כל Push מפעיל בדיקות+Build. Continuous Deployment = אחרי CI מוצלח, הקוד נפרס אוטומטית. ביחד: כל שינוי שעובר בדיקות מגיע למשתמשים אוטומטית.
שאלה 2.1 · מפתח שם את VITE_STRIPE_SECRET_KEY בקובץ .env ומשתמש בו בקוד React. מה הבעיה?
- א. הקידומת חייבת להיות REACT_APP ולא VITE
- ב. אין כל בעיה
- ג. אפליקציית React רצה בדפדפן
- ד. קובץ ה-env אינו נקלט כלל ב-Vite
✓ תשובה: ג) — כל VITE_* variable נכלל ב-Bundle ומגיע לדפדפן המשתמש. Stripe Secret Key שנחשף = גישה לחשבון התשלומים. Secrets חיים ב-Backend בלבד.
שאלה 2.2 · ב-Vite, איזה prefix חייב להיות לכל Environment Variable שרוצים לגשת אליו מה-React?
// vercel.json — חובה לReact Router (SPA Routing)!
// ללא זה, Refresh ב-/about יחזיר 404
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}- א. REACT_APP כקידומת הנדרשת למשתנה
- ב. PUBLIC כקידומת הנדרשת למשתנה הסביבה
- ג. אין צורך בקידומת כלשהי למשתנה הסביבה
- ד. VITE
✓ תשובה: ד) — ב-Vite: VITE_*. ב-CRA: REACT_APP_*. Variables ללא ה-prefix לא נחשפים ל-Browser — מנגנון אבטחה מכוון.
שאלה 3.1 · משתמש רענן את הדף ב-/about ב-React App שפרסתם ב-Vercel וקיבל שגיאת 404. מה הסיבה?
- א. חסר קובץ vercel.json עם הגדרת rewrites
- ב. עמוד ה-about אינו קיים כלל בפרויקט
- ג. React Router אינו תומך בפלטפורמת Vercel
- ד. צריך להשתמש ב-HashRouter במקום BrowserRouter
✓ תשובה: א) — React Router מנהל ניווט ב-JavaScript. Refresh שולח בקשה לשרת ל-/about. ללא rewrites, Vercel מחפש about.html שלא קיים. הפתרון: vercel.json שמפנה הכל ל-index.html.
שאלה 3.2 · מה הוא needs: test ב-GitHub Actions Workflow?
import { lazy, Suspense } from 'react';
// במקום: import AdminPage from './AdminPage'; // נטען תמיד
// עם Lazy: נטען רק כשמנווטים לRoute
const AdminPage = lazy(() => import('./AdminPage'));
const SettingsPage = lazy(() => import('./SettingsPage'));
function App() {
return (
<BrowserRouter>
{/* Suspense: מה להציג בזמן הטעינה */}
<Suspense fallback={<div>טוען...</div>}>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/admin' element={<AdminPage />} />
<Route path='/settings' element={<SettingsPage />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}- א. תלות בין ה-Jobs
- ב. פקודה להתקנת ספריית Jest בסביבה
- ג. הגדרת מספר ה-Runners להרצה במקביל
- ד. ציון שה-Job זקוק לסביבת Node.js לריצה
✓ תשובה: א) — needs: test יוצר תלות: Deploy רץ רק אחרי שה-test Job הסתיים בהצלחה. כך מבטיחים שלא פורסים קוד שנכשל בבדיקות.
שאלה 4.1 · מה עושה lazy(() => import('./AdminPage'))?
- א. מכביד על הטעינה הראשונית של האפליקציה
- ב. פיצול קוד
- ג. מגביל את הגישה לדף רק למשתמשים מנהלים
- ד. מאחד את כל הקומפוננטות לתוך קובץ אחד
✓ תשובה: ב) — lazy() יוצר Code Split: הדפדפן לא יוריד את AdminPage עד שמישהו מנווט ל-/admin. הטעינה הראשונית של האפליקציה מהירה יותר.
שאלה 5.1 · מהו עיקרון DRY?
- א. אל תחזור על עצמך, כלומר כל לוגיקה תופיע במקום אחד בלבד
- ב. אל תשתמש ב-CSS ישירות אלא השתמש ב-Tailwind
- ג. כתוב את הקוד קצר ככל האפשר תמיד
- ד. תמיד כתוב את הבדיקות לפני הקוד עצמו
✓ תשובה: א) — אם יש לך לוגיקה זהה בשני מקומות ומגיעה תקלה — צריך לתקן פעמיים. אם שכחת לתקן באחד = באג. DRY אומר: חלץ לפונקציה משותפת ורפרנס ממקום אחד.
שאלה 5.2 · מה בעיית Magic Number? כגון if (role === 2)
- א. מספרים גורמים לביצועים איטיים יותר בהרצה
- ב. TypeScript אינו תומך בהשוואת מספרים עם שלושה סימנים
- ג. אין כל בעיה
- ד. המספר 2 אינו מסביר דבר
✓ תשובה: ד) — הפתרון: const ROLES = { ADMIN: 2 } ואז if (role === ROLES.ADMIN). עכשיו הקוד מסביר את עצמו ואם הערך משתנה — מעדכנים במקום אחד. 🎯 דגש למבחן — היסטוריית Git היא כרטיס ביקור מקצועי: הודעות כמו "fix", "asdf" או "update" פוסלות אתכם בעיני בוחן. כתבו הודעה שמתארת מה השתנה ולמה (feat: add user login validation).
שאלה 5.3 — ראיון · בוחן שואל: "מה החולשה הכי גדולה בארכיטקטורת הפרויקט שלך?" — מה התגובה הנכונה?
def parse_env(text: str) -> dict:
env = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith('#'): continue
if line.startswith('export '): line = line[7:].strip()
if '=' not in line: continue
k, v = line.split('=', 1); k = k.strip(); v = v.strip()
if len(v) >= 2 and v[0] == v[-1] and v[0] in "\"'":
v = v[1:-1]
env[k] = v
return env- א. לזהות חולשה אמיתית ולהסביר מדוע היא קיימת וכיצד אפשר לפתור אותה
- ב. לומר שהפרויקט מושלם ואין בו כל חולשות
- ג. לומר בפשטות אינני יודע לענות על כך
- ד. להסיט את הנושא לכיוון נושא אחר לגמרי
✓ תשובה: א) — בוחנים לא מחפשים קוד מושלם, אלא יכולת חשיבה ביקורתית. "הבאתי את כל הנתיבים ב-Component אחד — בפרויקט גדול הייתי מפצל ל-services נפרדים" = תשובה מקצועית ובוגרת. 🎯 דגש למבחן — הימנעו מ-Over-engineering: במבחן קוד מעשי כתבו את הפתרון הפשוט שעובד. השתמשו במחלקות, ירושה ו-Design Patterns רק כשהמשימה באמת מחייבת זאת.