AMD היא תבנית עיצוב חדשה-יחסית, המיושמת בעיקר בג’אווהסקריפט ובאה לסייע לכתוב כמות גדולה של קוד ג’אווהסקריפט כך שיהיה קל לתחזוקה.
הדמיון ל MVC הוא במטרה – שמירה של סדר בעבודה עם כמות גדולה של קוד. דרך הפעולה – שונה לגמרי. אפשר (וכדאי) להשתמש גם ב MVC וגם ב AMD במקביל על מנת “לעשות סדר” בקוד הג’אווהסקריפט שלנו.
פוסט זה שייך לסדרה: MVC בצד הלקוח ובכלל.
הקדמה
בשנת 2009, בחור בשם קוין דנגור (Kevin Dangoor) יזם פרויקט בשם ServerJS. מטרת הפרויקט: להתאים את שפת ג’אווהסקריפט לפיתוח צד-השרת.
הפרויקט, כלל קבוצות עבודה שהגדירו APIs לצורת עבודה שתהיה נוחה ואפקטיבית בפיתוח ג’אווהסקריפט בשרת.
פרויקט ServerJS הצליח לעורר הדים והשפיע בצורה משמעותית על עולם הג’אווהסקריפט. פרויקטים מפורסמים שהושפעו ממנו כוללים את CouchDB, Node.js ו MongoDB.
ServerJS הצליח כל-כך, עד שגם הדפדפנים (שלהם הפרויקט לא-יועד) החלו לממש רעיונות מתוך ServerJS, בהתאמה קלה לעולם הדפדפנים. אנשי ServerJS קיבלו את האורחים החדשים, ושינו את שם הפרויקט ל: “CommonJS”. כלומר: הפרויקט של כ-ו-ל-ם.
עם אלו בעיות ServerJS מנסה להתמודד?
- הגדרה של מודולים (modules) וחבילות (packages) בכדי לארגן את הקוד. חבילות הן קבוצות של מודולים.
- כתיבת בדיקות-יחידה.
- כלי עזר לכתיבת קוד אסינכרוני כך שהקוד יישאר מודולרי – אותו כיסיתי בפוסט מקביליות עם jQuery (ובכלל) (תקן ה Promises/A, היחסית-מפורסם)
- עבודה עם מערכות קבצים.
- טעינה דינמית של קוד.
- ועוד כמה…
כיום require.js היא הספרייה הנפוצה ביותר, בפער גדול, על שאר האלטרנטיבות. המצב מזכיר במעט את המצב של jQuery מול MooTools או Prototype – תקן “דה-פאקטו”.
היתרונות של AMD (בעצם: require.js)
מלבד היכולת להפציץ חברים לעבודה במושגים (כמו CommonJS, AMD או Modules/1.1), תבנית-העיצוב AMD מספקת יתרונות משמעותיים לאפליקציות גדולות. מרגע זה ואילך אתייחס ספציפית ל require.js, או בקיצור: “require”.
#1: ניהול “אוטומטי” של תלויות בין קובצי javaScript
האם קרה לכם שהיה לכם באג בקוד שנבע מסדר לא-נכון של תגיות ה ב head של קובץ ה HTML?
קרוב לוודאי שבמערכת גדולה יהיו מספר רב של קובצי javaScript ולכן – מספר רב של תלויות. אין דרך ברורה להגדיר קשרים בין קבצי javaScript (כולם נרשמים במרחב זיכרון משותף), כל שקשרים אלו הם לא-מפורשים ואינם קלים לתיעוד או למעקב.
שימוש ב require פוטר אתכם מדאגה לעניין זה. require תטען את הקבצים בסדר הנכון, ע”פ ההגדרות שסיפקתם.
עבור אלו ששמרו על קובצי javaScript ענקיים, require מאפשרת להרבות בקבצים ללא דאגה לניהול שלהם – כך שיהיה קל יותר לפתח את הקוד.
הערה קטנה: require לא נבנתה לנהל קשרים בהם יש cycles, אולם יש “טכניקה” בה ניתן לטעון קבצים עם תלות מעגלית – אם כי בצורה מעט מסורבלת.
#2: טעינה עצלה ומקבילית של קבצי javaScript [ביצועים]
Require מנצלת את היתרון שהגדרתם כבר את התלויות בין הסקריפטים לא רק בכדי לטעון אותם בצורה נכונה, כי גם בכדי לטעון אותם בצורה אופטימלית מבחינת ביצועים.
- Require לא תטען קובץ עד לרגע שצריך אותו בפועל (lazy loading).
- Require טוענת קבצים בעזרת תכונת ה async של תגית ה – משהו שכמעט בלתי-אפשרי לנהל באופן ידני בפרוייקט גדול.
משהו שבשפות אחרות היינו מקבלים כמשפט “import” או “include” ולעתים היה נראה כמעמסה בעת כתיבה – מתגלה כחשוב מאוד כשהוא חסר.
הניסיונות שלי לנהל פרוייקטים בעזרת namespaces במרחב הגלובלי (בצורת {} || var myns = myns) נגמרו לבסוף בעשרות תלויות בלתי-רצויות בקוד ש”הזדחלו” מבלי שהרגשנו. ברגע שרצינו להשתמש במודולריות של הקוד, כפי שתכננו – לא יכולנו לעשות זאת ללא refactoring משמעותי.
מבוא קצר ל Require.js
מבוא נאיבי ל Require.js
- define – הגדרה של מודול (יחידת קוד של ג’אווהסקריפט בעלת אחידות גבוהה ותחום-אחריות ברור).
- require – בקשה לטעינה של מודול בו אנו רוצים להשתמש.
- require.config – הגדרות גלובליות על התנהגות הספרייה.
ModuleID הוא מזהה טקסטואלי שם המודול. ה Id בעזרתו אוכל לבקש אותו מאוחר יותר.
את הקוד של המודול כותבים בתוך פונקציה, כך שלא “תלכלך” את המרחב הגלובלי (global space).
קונבנציה מקובלת ומומלצת היא לחשוף את החלק הפומבי (public) של המודול בעזרת החזרת object literal עם מצביעים (ורוד) לפונקציות שאותם ארצה לחשוף (טורקיז). כמובן שאני יכול להגדיר משתנים / פונקציות נוספים שלא ייחשפו ויהיו פרטיים.
מבנה זה נקרא “Revealing Module” והוא פרקטיקה ידועה ומומלצת בשפת javaScript. ספריית require מסייעת להשתמש במבנה זה. דיון מפורט במבנה זה ניתן למצוא תחת הפסקה “הרצון באובייקטים+הכמסה = Module” בפוסט מבוא מואץ ל JavaScript עבור מפתחי Java / #C מנוסים – חלק 2.
נניח שיש לי קוד שזקוק למודולים 1 ו 2 בכדי לפעול, כיצד הוא מתאר תלות זו וגורם להם להטען?
פשוט מאוד:
הפונקציה בדוגמה היא callback שתפעל רק לאחר שהקוד של מודולים 1 ו2 נטען ואותחל. m1 הוא reference ל מודול1 (אותו object literal שהוחזר ב return וחושף את החלקים הציבוריים) ו m1 הוא reference למודול2. השיוך נעשה ע”פ סדר הפרמטרים.
doStuff היא כבר סתם פונקציה שעושה משהו עם m1 ו m2.
בפועל, רוב הפעמים יהיו לנו קוד שגם:
- מגדיר מודול.
- וגם תלוי במודולים אחרים.
משהו שנראה כך:
קוד זה הוא מעט מסורבל, ויותר גרוע – טומן בתוכו חשיפה לאופי האסינכרוני בו טוענת require את קובצי ה javascript. ייתכן וה return יופעל לפני ש doStuff הוגדרה – מה שיחזיר undefined כמצביע ל doStuff לקוד שביקש אותו.
כתיבת קוד אסינכרוני שתבטיח שה return יופעל רק לאחר ש doStuff הוגדרה תוסיף עוד מספר שורות קוד – ותהפוך את קטע הקוד למסורבל עוד יותר. על כן require (בעצם AMD) הגדירה תחביר מקוצר למצב של מודול שתלוי בקוד אחר. זהו בעצם המצב הנפוץ ביותר:
הנה, קוד זה כבר נראה אלגנטי וקצר. הפונקציה שהגדרנו בשורה הראשונה היא ה callback שיקרא רק לאחר שמודולים 1 ו2 הופעלו – ממש כמו בפקודת require.
בעצם, ניתן לחשוב על פקודת require כמקרה פרטי של define בו איננו רוצים להגדיר מודול.
היא שימושית ב-2 מקרים:
- כאשר אנו רוצים לטעון מודולים רק בהסתעפות מסוימת בקוד (ולכן איננו יודעים בוודאות על צורך זה בשורה הראשונה).
- עבור הקובץ הראשון בתוכנה שלנו. כלומר: פונקציית ה “main”.
קוד זה מגדיר שאם קובץ לא נטען (ברשת) תוך 10 שניות, require יוותר ויזרוק exception. מצב זה סביר בעיקר כאשר אתם טוענים קובץ מאתר מרוחק. ה default הוא time-out של 7 שניות.
זהו, סיימנו!
ליותר מזה לא תזדקקו אלא אם אתם מתכננים לכתוב מערכת הפעלה חדשה, או את קוד הגשש שיפעל על מאדים…
או לבצע בדיקות-יחידה, להשתמש בספריות חיצוניות, לנהל גרסאות שונות של קבצים או בעצם…. להשתמש ב require בפרויקט אמיתי.
סיכום
חטאתי בהפשטה של require ואופן העובדה שלה, אולם בכל זאת – צריך להתחיל איפהשהו.
את החטא אני מתכוון לתקן, אולם הפוסט הולך ומתארך ולכן אתחיל פוסט המשך.
שיהיה בהצלחה!
מעולה כתמיד.ראוי לציין שעד כמה שזה נוח בסביבת פיתוח, ב-production מדובר על הרבה מאוד קריאות לשרת ככל שהמודולים נטענים, אז מומלץ להשתמש בכלי שיבצע build – כלומר יבחן את ה-dependencies ויכין קובץ JS אחד מקומפרס. (לדוגמה Google Closure Compiler).* כמובן שאני מדבר על פיתוח לדפדפן
אם מדובר בsingle page app, מודול מכיל בנוסף ל javacript גם css ו hmtl. איך מתמודדים עם זה?
תודה על התגובה.מסכים לגמרי.r.js (שהזכרתי למעלה) מבצע גם איחוד לקובץ גדול ע\”פ ניתוח התלויות וגם מבצע minification. היתרון היחסי שלו הוא שהוא יודע לקרוא את התלויות שהוגדרו ב require, כל שהוא יכול לאחד 70% מהקוד, וששאר הקוד (שלא תמיד נדרש) ייטען באופן דינאמי.ליאור
נקודה טובה:ישנו Plugin ל require שנקרא text.js שיכול לטעון דינמית גם קובצי css וגם snippets של HTML (אני מניח שדיברת על templates. אם אתה מעוניין קובץ HTML ראשי אחר אזי פקודת location.replace בג'אווהסקריפט תעשה את העבודה).הנה הלינק ל text.js, אנסה לכסות אותו בפוסט ההמשך:https://github.com/requirejs/textליאור
צודק, פספסתי את החלק הזה במאמר.
רוב תודות :)הצלחת לסקרן אותי, אז חיפשתי ונתקלתי ב curl, שגם היא ספריית amd עם טיפול טוב גם ב css ו טמפלטים ומה לא : https://github.com/cujojs/curl
curl, עד כמה שאני יודע היא המימוש השני הכי נפוץ של AMD אחרי require.השורשים של curl, כמו require, הם מספרייה שנקראת dojo – ולכן לא אתפלא אם הן ממש חולקות קוד.סה\”כ נראה של require יש קהילה גדולה יותר ומספר גדול יותר של כלים / ספריות בעלות אינטגרציה ספציפית ל require. כרגע עולה לי בראש Karma – אך אני בטוח שיש עוד.ספריה ידועה אחרת היא lsjs (אם כי אינני מנסה לעשות השוואה בין הספריות השונות). אם כוללים ספריות התואמות ל CommonJS וספריות לטעינה דינאמית של משאבים ללא תאימות מיוחדת – המבחר הוא גדול מאוד.ליאור
מעניין אותי לדעת, איך נראה amd בjs טהור, בלי ספריה?
עליך להגדיר את פקודות ה define וה require בעצמך.require.config הוא עדיין לא AMD סטנדרטי, אם כי זו הצעה בעבודה.המימוש הכי פשוט ו\”נקי\” של AMD שידוע לי הוא אלמונד:https://github.com/jrburke/almondהקוד קצר יחסית וקריא – אתה יכול פשוט לעבור עליו.התחלתי לעבוד על פוסט המשך שם אנסה להזכיר נושאים אלו.ליאור