פיתוח Solid.js: איפה נשברת הביצועים
Solid.js בונה ממשק על ריאקטיביות דקה: סיגנלים יודעים איזה צומת תלוי בהם, ומעדכנים רק אותו — אין כאן עץ וירטואלי שצריך לבנות מחדש ולהשוות. לכן ביצועי הממשק בטפסים ובכרטיסים נשארים יציבים גם במכשירים חלשים, והכול נשבר בקטעי קוד ספציפיים.
הקטע הראשון — רשימות וטבלאות כבדות. כל עוד יש עשרות שורות, הכול צפוי; כאשר בטבלה נכנסות אלפי רשומות עם סינון, מיון ופרטים מתרחבים, צוואר הבקבוק של הפרונטאנד עובר ליצירת צמתים ולחישוב מחדש של הסדר.
וירטואליזציה, מפתחות שורה יציבים והימנעות מיצירה מחדש של מארקאפ בעת שינוי סינון מחזירים תגובה יציבה.
השני — עדכוני מצב תכופים. מדדים חיים, פיד הודעות, גרירת אלמנטים: כל כתיבת סיגנל מושכת איתה באופן סינכרוני חישובים נגזרים ואפקטים, ובלי קיבוץ עדכונים מהירות התגובה יורדת בלי סיבה נראית לעין.
השלישי — רינדורים מיותרים בגלל מצב גס מדי. סיגנל משותף אחד לעמוד גורם לכל המנויים להתחשב מחדש: עריכת שדה בכותרת נוגעת בטבלה בתחתית. הפתרון הוא גרנולריות — מצב ברמת הקומפוננטה, נגזרים ממואיזציה מופרדים ומנויים ממוקדים.
הרביעי — הזמן עד לתגובה הראשונה: במכשיר חלש הוא מורכב מהיקף המארקאפ, החייאתו בצד הלקוח ועבודת הקוד עד למגע הראשון. נסביר איך סוגרים את זה: מה לפרופל, היכן לעשות וירטואליזציה ואיך לפרק את המצב כך שהממשק יגיב מיד.
מה משתנה במספרים: תגובה, גודל קוד ובנייה
מודל הריאקטיביות של Solid.js לא בונה עץ ייצוג ביניים: החישובים נקשרים לצמתים קונקרטיים, ולכן כשהנתונים משתנים מתעדכנים רק חלקי המארקאפ שתלויים בהם. מכאן נובעות שלוש תוצאות מדידות — זמן התגובה לפעולה, גודל הקוד הסופי וזמן הבנייה של הפרויקט.
זמן התגובה לפעולה יורד, כי נעלם הרינדור המפל. לחיצה על מסנן פעם הפעילה בנייה מחדש של הבלוק כולו, כעת מחושבת מחדש רק שורת התוצאה — מספר הפעולות על המארקאפ מצטמצם פי כמה. בטבלאות ובקטלוגים ארוכים זה מורגש במקומות שבהם הממשק בדרך כלל "נתקע": גלילה, סינון, פתיחת כרטיס.
גודל הקוד הסופי קטן יחד עם השכבה שכבר אין צורך לשלוח לדפדפן. חלק מהלוגיקה עובר לשכבת הנתונים ומפסיק להיות משוכפל בקומפוננטות, הבנייה מורכבת ממספר קטן יותר של מודולים — מכאן זמן בנייה קצר יותר לפרויקט וארטיפקט קומפקטי יותר לפריסה.
יציבות הממשק היא תוצאה של אותו סדר עדכונים: המצב משנה צומת אחד, הצמתים השכנים לא נוצרים מחדש, והממשק לא "קופץ" בפעולות מהירות של המשתמש.
כל שינוי אנחנו מתעדים במדידות לפני ואחרי: לוקחים פרופיל רינדור בתרחישים טיפוסיים, מחשבים את שיעור הצמתים המתעדכנים, גודל הבנייה ומשך המעבר שלה. כך החלפת הארכיטקטורה מאושרת במספרים, לא בהבטחות.
פיתוח Solid.js: השוואת גישות לרינדור
בחירת מצב הרינדור היא לא שאלה של טעם, אלא תוצאה של השאלה כמה מהר צריך להופיע הרינדור הראשון וכמה לוגיקה באמת חיה בדפדפן.
ב-Solid.js ריאקטיביות ממוקדת מרנדרת מחדש רק את הצומת שהשתנה, ולכן אותו קוד עובד גם ברינדור בצד השרת וגם בהחייאת עמוד הדרגתית — רק אופן אספקת המארקאפ ללקוח משתנה.
להלן ארבע אפשרויות עבודה, שביניהן אנחנו בוחרים בתחילת הפרויקט. הגבול ביניהן עובר לפי אינטראקטיביות: ככל שיותר בלוקים עוברים החייאה בצד הלקוח, כך יקר יותר פיצול הקוד לחלקים וכך בזהירות רבה יותר צריך לתכנן את החזרה לאחור.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| רינדור בצד השרת עם החייאת כל העץ | אזורים אישיים ולוחות שבהם כמעט כל בלוק הוא אינטראקטיבי | המארקאפ מגיע מהר, אבל עד להחייאה לחיצות לא עובדות; חלק הלקוח גדול |
| החייאת עמוד הדרגתית, באיים | דפי פרומו, קטלוגים, מדורים עם ווידג'טים אינטראקטיביים נדירים | גבולות האיים נקבעים מראש; מצב משותף ביניהם מועבר ידנית |
| הגשה סטטית עם החייאה ממוקדת | מדורי עזר, תיעוד, מדורי תוכן ושיווק | אין לוגיקת שרת חיה: כל מה שתלוי בנתוני המשתמש עובר לבקשה נפרדת |
| רינדור בצד הלקוח בלבד | ממשקים פנימיים מאחורי אימות, שבהם נראות במנועי החיפוש לא חשובה | מסך ראשון ריק וטעינה חובה של קוד הלקוח לפני כל רינדור |
המצבים תואמים זה לזה: מדורים סטטיים מוגשים כמארקאפ מוכן, והחיפוש והאזור האישי מתחברים בחלקים נפרדים עם החייאה משלהם. מערכת כזו מפחיתה את גודל קוד הלקוח במסך הראשון ומשאירה מרחב לסיבוך הדרגתי של הממשק.
שלבי העבודה: מפרופיילינג ועד הטמעה בבנייה
הריאקטיביות של Solid.js נותנת יתרון רק במקום שבו רואים מראש אילו צמתים מתעדכנים בכל שינוי מצב. לכן אנחנו מתחילים לא משכתוב המארקאפ, אלא ממדידות: בלעדיהן התיקונים הופכים לסידור מחדש של קוד בלי תוצאה ניתנת לבדיקה.
- פרופיילינג ממשק. לוקחים מדידות במסכים אמיתיים: כמה מהר מופיעה התגובה הראשונה, כמה נמשך פריים בעת סינון רשימות ארוכות, ואיפה הרינדור מחדש נוגע בענפים מיותרים של העץ.
- תרשים תלויות. בונים גרף של סיגנלים, ערכים מחושבים ואפקטים — נהיה ברור אילו נתונים גוררים שרשרת עדכונים ואילו חיים בבידוד.
- תוכנית הטמעה. מחלקים את המעבר לשלבים כך שכל אחד יהיה אפשר להעלות ולחזור ממנו בנפרד, בלי לעצור את העבודה על שאר חלקי המוצר.
- עיבוד מחדש של קומפוננטות. מעבירים קטעים כבדים לעדכון ממוקד, מרימים מצב מקינון עמוק, מסירים מנויים מיותרים וחישובים חוזרים.
- אינטגרציה עם הבקאנד הקיים. חוזים, אימות וסכמות תגובה נשארים כשהיו — השכבה החדשה אחראית רק על מצב ותצוגה.
- בדיקה בתרחישים אמיתיים. מריצים מסלולי משתמש טיפוסיים ושיאיים, משווים מדידות לפני ואחרי, מכסים רגרסיות שנמצאו בבדיקות אוטומטיות.
- מסירה. מעבירים לצוות כללי עבודה עם מצב, תרשים תלויות וסט בדיקות, כדי שהאפקט יישמר בהמשך הפיתוח.
ארטיפקטים למסירה: תרשימים, כללי בנייה, תיעוד
אחרי המסירה הכול נשאר במאגר של הלקוח ובתיעוד שמקושר לקוד. מסירת פרויקט היא לא ארכיון ולא שיחת סיכום מהזיכרון: כל מהנדס שפותח את המאגר צריך להבין איך נבנית האפליקציה, איפה חי המצב ומה אפשר לשנות בלי סיכון.
- תרשים קשרים ריאקטיביים: אילו סיגנלים יוצרים חישובים, איפה נשמר המצב, אילו צמתים מתעדכנים משינוי אחד — בצורת דיאגרמה וטבלת "מקור → צרכנים".
- קונפיגורציית הפרויקט וכללי בנייה: פרמטרי קומפילציה, הפרדה בין חלק השרת לחלק הלקוח, משתני סביבה לפי סביבות, פקודות בנייה והרצה מקומית.
- מוסכמות לקומפוננטות: איפה מותר מצב מקומי, איך מעצבים פרופרטיז, איך נקראים קבצים ומודולים, מה אסור בתוך ערכים מחושבים.
- קטלוג קומפוננטות מוכנות עם תיאור פרופרטיז, דוגמאות שימוש והערות לגבי אילו שינויים בטוחים ואילו שוברים צרכנים.
- נוהל תחזוקה: סדר ותדירות עדכון תלויות, קריטריונים לחזרה לגרסת בנייה קודמת, אחראים על מודולים.
- נוהל בדיקות: אילו בדיקות אוטומטיות עוברות לפני שחרור — טיפוסים, בדיקות התנהגות, תקציב גודל בנייה.
- חומרי הדרכה: הקלטת סקירת קוד עם צוות הלקוח והוראות קצרות למשימות טיפוסיות — להוסיף עמוד, לשנות טופס, לחבר בקשה חדשה.
מעבירים על קוד חי: סוקרים את המבנה יחד עם מהנדסי הלקוח, עונים על שאלות לכל סעיף ברשימה. גישה למאגר ולתיעוד — בידי צוות הלקוח, בלי תלות בנו.
מקרה בוחן: לוח בקרה כבד ומדדים לפני ואחרי
לוח הבקרה אסף נתונים ממספר מקורות: גרפים מסכמים, יומן אירועים וטבלה עם עשרות אלפי שורות וסינונים. כל עדכון של הזרם רינדר את העץ כולו — הלשונית נתקעה לכמה שניות, והאגרגטים בכותרת לזמן קצר לא תאמו את תוכן הטבלה. המשתמשים הפסיקו לתת אמון במספרים ורעננו את העמוד ידנית, מה שהחזיר אותם לאותו מצב.
מה שינינו
את המצב העברנו לריאקטיביות דקת-גרגר של Solid.js: מנויים נקשרו לפרוסות נתונים ספציפיות, ולא למאגר השורש. את הטבלה מסרנו לוירטואליזציה, ערכים נגזרים כבדים — לממואיזציה, עדכוני הזרם — לעיבוד באצווה בפריים רינדור אחד. הווידג'ט מתעדכן רק כשהמקור שלו עצמו משתנה, ולא כל אחד מעשרות השכנים.
| מדד | לפני | אחרי |
|---|---|---|
| תגובה לסינון יומן | 4.8 שנ' | 0.7 שנ' |
| רינדורים לעדכון זרם | ~1200 | 18 |
| זמן בניית הפרויקט | 96 שנ' | 34 שנ' |
| חוסר סנכרון בין אגרגטים לטבלה | כל הזמן | לא משוחזר |
התגובה חזרה לשברירי שנייה, הלוח מחזיק זרם חי בלי לחסום את הממשק. העיקר — נעלם סוג כשל שאנחנו קוראים לו חוסר סנכרון בתצוגה: פער בין המספרים המסכמים לפירוט כבר לא נוצר. את התרחיש הזה קיבעו בבדיקות רגרסיה, כדי שלא יחזור יחד עם עדכונים עתידיים.
מה לעשות אם הפרונטאנד כבר כתוב בטכנולוגיה אחרת
אין צורך לשכתב הכול בבת אחת, ולרוב זו האפשרות הגרועה ביותר. מעבר הדרגתי משתלם יותר במקום שבו המוצר חי ומכניס הכנסות: הקוד הישן ממשיך לשרת תעבורה, והשכבה החדשה מופעלת עמוד-עמוד, לפי נתיבים.
הצעד הראשון — לחלק את העמודים לפי קריטיות ומידת קישוריות. קודם כל מעבירים קטעים בעומס גבוה וקשרים חלשים עם שאר האפליקציה: קטלוג, רשימות, כרטיסי מוצר, שלבים בודדים של השלמת הזמנה. את האזור האישי ואת ממשק הניהול משאירים לאחר כך — שם יש יותר מצבים והרשאות, וטעות יקרה יותר.
את השכבה החדשה מכניסים בגבול הנתיב, ולא בתוך מסכים קיימים. שתי האפליקציות חיות זו לצד זו תחת אותו דומיין, חולקות סשן, אימות ושכבת בקשות לשרת, והמעברים ביניהן נשארים בלתי מורגשים למשתמש.
מערכת עיצוב משותפת וחוזה אחד עם חלק השרת — אלה ששומרים על תאימות השכבות ולא נותנים לממשק להתפצל במראה ובהתנהגות.
המוצר לא נעצר: שחרורים ממשיכים כרגיל, מעבר עמוד למימוש חדש נעשה בעריכת נתיב, והחזרה לאחור — בהחזרת הקודם. Solid.js במיגרציה כזו של פרונטאנד מראה את עצמו היטב: הוא מתחבר לצמתים בודדים בעמוד, בלי לדרוש אפליקציית שורש אחת לכל האתר.
לעיבוד חלקי יש מחיר. במשך זמן מה קיימות שתי בנייות, חלק מהתלויות משוכפל, והצוות מחזיק בראש את שני המודלים. לכן אנחנו מחשבים מראש את שיעור העמודים שבאמת יעברו, ואומרים ישירות מתי משתלם יותר לשכתב הכול מאשר לתחזק תאימות במשך שנים.
ספקות טכניים: אינדוקס, תלויות, תמיכה
ההתנגדויות לסטאק הפרונטאנד מצטמצמות בדרך כלל לארבע: האם העמודים ייכנסו לאינדקס, מה יהיה עם עדכון תלויות, מי יתמוך בקוד וכמה מהר צוות חדש ייכנס אליו. נבחן כל אחת מהן מהצד ההנדסי, בלי מילים כלליות.
העמודים ייכנסו לאינדקס של מנועי החיפוש?
כן, אם הרינדור מועבר לשרת. הריאקטיביות כאן עובדת מעל צמתים רגילים של המסמך, ולכן העמוד מוגש כ-HTML מוכן — רובוט החיפוש מקבל תוכן, מטא-תגים ומיקרו-מארקאפ כבר בתגובה הראשונה, בלי לחכות להרצת סקריפטים. סטטיקה אנחנו בונים בשלב הבנייה: אין צורך בחלק שרת, וזמן התגובה יורד.
עדכון תלויות לא ישבור את הפרויקט?
הליבה של Solid.js קטנה בכוונה, ורוב הלוגיקה חיה בקוד היישום, לא בעץ החבילות. עדכונים יוצאים לעתים רחוקות יותר ופוגעים פחות בממשקים ציבוריים. מעברים מז'וריים מתכננים כמשימה נפרדת עם הרצת בדיקות אוטומטיות על עותק של הבנייה — השחרור לא תלוי בכך שספריות צד שלישי הספיקו להתעדכן.
האם תמיכה בקוד זר יכולה להתנהל בלי המחבר?
קומפוננטות הן פונקציות רגילות עם תלויות מפורשות: אין מחזור חיים נסתר שצריך להחזיק בראש. הלוגיקה מופרדת מהמארקאפ למודולים נפרדים ומכוסה בבדיקות, ולכן מהנדס חדש משנה התנהגות ולא מפרק אפקטים. בנוסף אנחנו מעבירים תרשים נתונים ומפת קומפוננטות.
עד כמה גבוהה נקודת הכניסה לצוות?
התחביר קרוב למה שמפתחים כבר ראו בספריות אחרות, ולכן המעבר אורך ימים ולא חודשים. ההבדל המרכזי — ניהול מצב באמצעות סיגנלים; אותו לומדים כבר במודול קטן ראשון. אחר כך הצוות עובד בקצב רגיל: קומפוננטה, בדיקה, ריוויו.
נדון במשימה: איך אנחנו מתחילים לעבוד
כדי שההערכה תתבסס על עובדות ולא על ניחושים, אנחנו צריכים מינימום הקשר: מה המוצר, אילו תרחישים קריטיים ומה כבר מיושם. לפי התיאור הזה אנחנו מנתחים את המשימה וחוזרים עם תוכנית עבודה לפי שלבים, הרכב הצוות ומשך הזמן של כל שלב.
מה לשלוח או לספר:
- תיאור המוצר ושלושה-חמישה תרחישים מרכזיים שבהם אנשים משתמשים;
- עיצובים, אב טיפוס או קישור לגרסה הנוכחית;
- אילו מסכים צריכים להגיב מיד גם במכשירים חלשים ובחיבור איטי;
- רשימת שירותים חיצוניים לאינטגרציה: תשלום, התחברות לחשבון, אנליטיקה, החלפת נתונים;
- מה כבר כתוב ועל מה זה עובד — אם חלק מהממשק עשוי ב-Solid.js, נראה מה אפשר לעשות בו שימוש חוזר;
- מגבלות: תאריכים קשיחים, דרישות לפריסה, להרשאות ולהגנת נתונים;
- מי מצדכם מקבל את השלבים ובודק את התוצאה.
בתגובה אתם מקבלים לא טווח "מהתחלה ועד הסוף", אלא פירוק: לאילו שלבים העבודה מתחלקת, כמה נמשך כל שלב, מי בצוות אחראי עליו ובאיזו נקודה אתם מקבלים את התוצאה.
אם במהלך הניתוח עולים סיכונים — למשל צוואר בקבוק בהחלפת נתונים או ספק שירותים לא יציב — אנחנו מציינים אותם מיד, לפני ההתחלה, ולא באמצע העבודה.
תארו את המשימה בשתי פסקאות או שלחו את מה שצוין — נחזור עם הערכה ותוכנית עבודה.







