למה Javascript מרגישה לחלקנו כמו סינית?

בשנים האחרונות עברה שפת Javascript מהפכה של ממש. משפה המשמשת לכתיבת סקריפטי צד לקוח קצרצרים היא התפתחה לשפה בוגרת, עם קהילת קוד פתוח עצומה ופעילה המורכבת ממפתחי צד שרת, צד לקוח ומובייל כאחד. מוצרים רבים מבוססים עליה, ונדמה שבכל יום רואה אור טכנולוגיה חדשה מבוססת Javascript.

למרות כל אלו, נדמה שעבור קבוצה לא קטנה של מפתחים, צעירים ומנוסים כאחד, קוד Javascript עדיין נראה כמו סינית, ואפילו מפתחים שהתנסו במספר רב של שפות תכנות "קלאסיות", עדיין מתקשים לקרוא ולהבין קוד JS.

בתור מישהו שכותב Javascript כבר שנים רבות, הבעיה הזאת תמיד סיקרנה אותי. הרי התחביר של Javascript דומה מאוד לשפות אחרות (כמו C++) ויכולותיה "out of the box" מצומצמות מאוד, אז מדוע מתכנתים רבים מתקשים בהבנתה? אחרי מחשבה רבה אני מאמין שמצאתי את שורש הבעיה.

אסינכרוניות מובנית

כדי להבין את שפת Javascript, חשוב להכיר את הבעיות אותן נועדה לפתור. שפת Javascript היא בראש ובראשונה שפת פיתוח ממשק משתמש גרפי (GUI), כלומר בשונה מתוכניות בשפות תכנות קלאסיות, המורכבות מקטע קוד יחיד הרץ בצורה סדרתית, ועוצר מדי פעם כדי לקבל קלט ממקור חיצוני כגון המשתמש או שרת מרוחק, שפת Javascript תוכננה כדי לעולם לא "לתקוע" את ממשק המשתמש.

ניקח לדוגמה את הפסאודו קוד הבא, הכתוב בסגנון קלאסי:

name = wait_for_user_input()
print "Welcome " + name

כאשר נקראת הפונקציה wait_for_user_input, היא מעבירה את השליטה למשתמש, ולא חוזרת עד אשר המשתמש סיים להזין את הקלט. בינתיים, שאר הקוד שלנו (ב-thread הנוכחי כמובן), לא ימשיך לרוץ.

אחד המאפיינים המעניינים והחשובים ביותר של שפת Javascript הוא הריצה ב-thread אחד בלבד. כלומר אם היינו כותבים קוד כזה ב-JS, כל ממשק המשתמש (אתר, אפליקציה וכד') היה נתקע עד לקבלת הקלט.

אז איך בכל זאת מאפשרת שפת Javascript פעולות המצריכות המתנה (למשתמש, לשרת וכד')? התשובה לכך היא, לדעתי, המפתח להבנת Javascript.

פונקציות Callback ותכנות מבוסס אירועים

ניקח את הדוגמה הקודמת ונשכתב אותה למשהו דומה יותר לקוד Javascript:

function handle_user_input(name) {
    console.log("Welcome " + name);
}

wait_for_user_input(handle_user_input);

למעשה שינינו שלושה דברים:

  1. הגדרנו פונקציה חדשה המטפלת בקלט שהגיע מהמשתמש. או במילים אחרות, הגדרנו event handler, כאשר האירוע (event) בו הוא מטפל הוא הגעת קלט מהמשתמש.
  2. העברנו את הפוקציה החדשה בתור פונקציית callback לפונקציה wait_for_user_input. כלומר, רק כאשר יתקבל הקלט מהמשתמש, הפונקציה שלנו שלנו תיקרא ותכתוב את הקלט ללוג.

ע"י ביצוע שינוי פשוט זה הפכנו את הקוד ל-non blocking, כלומר הקוד לא יעצור ויחכה לקלט, אלא ימשיך לרוץ וכאשר יגיע קלט – הוא יטופל מיד (כמובן שיש צורך לשנות את המימוש של wait_for_user_input, אבל כרגע זה פחות חשוב).

אז מה הופך את הקוד הזה לקשה להבנה? אני חושב ששום דבר. אבל הבעיה היא שפעמים רבות הקוד הזה יכתב, מטעמי חיסכון, בצורה מעט שונה:

wait_for_user_input(function(name) {
    console.log("Welcome " + name);
})

אני מאמין שהשינוי הקטן הזה הוא המפתח להבנת קוד Javascript. אז מה קרה פה? במקום להגדיר בנפרד את פונקציית ה-callback ולתת לה שם, הפכנו אותה לפונקציה אנונימית (anonymous function) והעברנו אותה ישירות לפונקציה wait_for_user_input.

השינוי הזה הפך את הקוד למשהו שלא דומה לקוד של אף שפת תכנות קלאסית!

מפתח שקורא את הקוד הזה צריך לא רק להכיר את התחביר, אלא להבין שהקוד, שבמבט מהיר נראה רציף, מחולק לשני חלקים – האחד נקרא מיד והשני בזמן לא ידוע בעתיד, אם בכלל.

ואם עד עכשיו התעסקנו בקוד פשוט לדוגמה, נסו לתאר לעצמכם מה עובר בראשו של מפתח שלא מכיר Javascript, הנתקל בקוד הבא:

document.getElementById("btn").addEventListener("click", function(evt) {
  if (validate_data()) {
    do_ajax_call(function (data) {
      if (data.success) {
        document.getElemenetById("next").addEventListener("click", function() {
          window.location.href = '/thank-you';
        });
      } else {
        console.log("error: " + data.msg);
      }
    });
  }
});

לא עוד Callback Hell

אז מה אפשר לעשות כדי לרכך את הנחיתה עבור מפתחים החדשים ל-Javascript? בשלב ראשון הם חייבים להכיר את רעיון ה-callbacks והטיפול באירועים, ואני מאמין שההסבר למעלה אמור להיות מספיק.

אך גם לנו המפתחים המנוסים יש יד בדבר – אני מאמין שע"י הקפדה על כלל בסיסי אחד בלבד ניתן להפוך כל קוד Javascript לידידותי יותר עבור מפתחים חדשים ואף עבור מפתחים מנוסים באותו צוות.

הסוד טמון בהפיכת הקוד שלנו ל-"רדוד", על ידי הפחתת השימוש בפונקציות אנונימיות, לדוגמה במקום לכתוב את הקוד הבא:

fetch_data_from_server(function(data) {
    // handle data
    fetch_more_data_from_server(more_data) {
        // handle more data
    }
});

ניתן שמות לפונקציות האנונימיות ונחלץ אותן החוצה:

fetch_data_from_server(handle_data);

function handle_data(data) {
    // handle data
    fetch_more_data_from_server(handle_more_data);
}

function handle_more_data(more_data) {
    // handle more data
}

לא רק שהקוד שלנו מסובך פחות, הוא גם מתעד את עצמו בצורה טובה יותר, זאת מכיוון שכעת יש שמות לפונקציות שמקודם היו אנונימיות, וברור יותר מה כל אחת מהן עושה.

אני לא טוען שאין מקום לשימוש בפונקציות אנונימיות כלל – לעיתים הן יכולות לחסוך קוד משוכפל או מיותר, אך יש לשים לב שאנחנו לא נגררים לכתוב קוד בצורת "משולש".

Callback Hell

לקריאה נוספת:

Redux – ארכיטקטורת Flux, אבל כמו שצריך

אם עבדתם אפילו קצת עם הספריה הנהדרת React מבית פייסבוק, כנראה שהבנתם (או לפחות ניסו להסביר לכם) את ההבדל בין מאפייני רכיב (props) למצב שלו (state). ואכן בפוסט המצויין Thinking in React אנו מוצאים הסבר מפורט על איך להחליט איזה מידע שייך לכל אחד מהם (בגדול, כל מה שיכול להגיע מבחוץ יהיה בפרופס, וכל מה שישתנה ע"י הרכיב וגם באחריותו יהיה ב-state).

ארכיטקטורת Flux, שהתפרסמה גם היא ע"י פייסבוק ביחד עם React, לוקחת את הקונספט של MVC צעד אחד קדימה, ע"י הגדרת מקומות המרכזים state גלובאלי, אלו הם ה-Stores. כדי לשמור על זרימת נתונים ברורה וצפויה, שינויים למידע ב-stores לא מתבצעים ישירות, אלא ע"י יצירת אירועים הנקראים פעולות (Actions), עליהם מאזינים ה-stores ומבצעים שינויים במידע בהתאם. ה-Actions נוצרים לרוב בעקבות פעולת משתמש, לדוגמה לחיצה על כפתור. על כל אלו מנצח ה-Dispatcher שדואג שהפעולות ישוגרו בסדר הגיוני ולא בו זמנית. רכיבי ה-React עצמם מאזינים לשינויים ב-stores, ומעדכנים את ה-state של עצמם בהתאם.

אם אתם לא סגורים על מה שקראתם עכשיו, אני ממליץ בחום על ההרצאה הקצרה הזאת של Jing Chen לפני שאתם ממשיכים לקרוא את הפוסט.

חשוב לציין ש-Flux עצמה היא רק design pattern, ולא תשתית קוד. למעשה הקוד היחיד המסופק ע"י פייסבוק הוא ה-Dispatcher, כל השאר הן רק מוסכמות. ולמרות ש-Flux חדשנית ומשתלבת יפה עם React, היא עדיין מרגישה מעט בוסרית ולא חלקה כמו שהיינו מצפים. לדוגמה, בכל רכיב (Component) שצריך גישה למידע ב-stores, אנחנו צריכים להירשם לקבלת עדכונים, ולעדכן את ה-state בכל שינוי:

function getTodoState() {
    return { allTodos: TodoStore.getAll() };
}

var TodoApp = React.createClass({
    getInitialState: function() {
        return getTodoState();
    },
    componentDidMount: function() {
        TodoStore.addChangeListener(this._onChange);
    },
    componentWillUnmount: function() {
        TodoStore.removeChangeListener(this._onChange);
    },
    _onChange: function() {
        this.setState(getTodoState());
    }
};

כמה boilerplate! וזה עוד לפני שהתחלנו לממש את render… הדוגמה הזאת (שהיא אגב, דוגמה רשמית של Flux) גם מעלה שאלות שהתשובות עליהן לא פשוטות בכלל:

  1. מה עם רכיבים ילדים? האם לחלחל אליהם את ה-state ידנית או שגם הם יאזינו ל-store?
  2. מה קורה אם השינוי ב-store לא היה רלוונטי לרכיב? האם להתעלם ולרנדר? אולי לבדוק את השינוי? אולי PureRenderMixin?
  3. מה קורה אם יש שני stores? צריך להירשם לשניהם? או אולי לאחד אותם? ומה אם הם תלויים אחד בשני?
  4. איך בוחרים איזה מידע ישב ב-store ואיזה ב-state של רכיבי ה-React עצמם?

חיסרון נוסף של Flux הוא הקושי לרנדר בצד השרת. מכיוון ש-stores הם סינגלטונים (וגם שומרים בתוכם את המידע עצמו), אין דרך פשוטה לייצר stores חדשים עבור כל בקשת HTTP ולאחסן את המידע שלהם בסופה.

Redux להצלה

אז למרות ש-Flux הוא לא הדבר הכי נוח בעולם, העיקרון של state יחיד גלובאלי המשיך להתפתח בקהילת הקוד הפתוח, ובמהרה החלו לצוץ חלופות רבות כגון: ReFlux, Fluxible, Flummox, Fluxette (נשבע לכם שאני לא ממציא) ו-Alt. אך נראה שהמנצחת הגדולה בהתקוטטות הזאת היא ספריית Redux מאת Dan Abramov (נשמע ישראלי, אבל הוא לא).

הספרייה הזאת (וגם כמה אחרות) לוקחת כמה צעדים קדימה את העיקרון של Flux:

  1. ה-store הוא יחיד, והוא מפוצל לשני חלקים: אובייקט state יחיד לכל האפליקציה, ופונקצייה טהורה אחת בשם Reduce המקבלת את אובייקט ה-state הנוכחי ו-action כלשהו, ומחזירה state חדש.
  2. ה-state היחיד הוא היררכי, וניתן להרכיב (לשלב) Reducers שונים כך שכל אחד יטפל בחלק אחר של ה-state. כך ניתן לפתח בנפרד חלקים שונים של האפליקציה.
  3. רכיבי React לא צריכים להכיל state בכלל. כל המידע שלהם צריך להיות מסופק בתור props מרכיב האבא ו/או מה-state הראשי.

שימו לב שהסעיף הראשון פותר בנקל את בעיית הרינדור בשרת, מכיוון שכעת ה-state הוא יחיד, גלובאלי, ואפשר לטעון אותו בתחילת כל בקשת HTTP ולשמור אותו חזרה בסופה, למשל בתור JSON.

בנוסף לשינוי בארכיטקטורה, הספריה מספקת מספר פונקציות בסיסיות, שביחד עם כמה מוסכמות, הופכות כל רכיב React לישות המורכבת (בגדול) מ-3 חלקים:

  1. פונקציית render, כמו שאנחנו כבר מכירים, שהיא לרוב פונקצייה טהורה, כלומר דטרמיניסטית ותלוייה אך ורק בקלט שלה ולא בשום מידע חיצוני.
  2. פונקציית mapStateToProps, שכשמה כן היא, מקבלת את ה-state הגלובאלי (ולעתים גם props מרכיב האבא) ומייצרת את ה-props הסופיים של הרכיב.
  3. אובייקט או פונקציה בשם mapDispatchToProps הממפה בין props של הרכיב ל-Action Creators, שהן פונקציות המחזירות אובייקט action אותו יש להעביר ל-reducer ובכך לשנות את ה-state.

אני יודע שזה יכול להישמע מורכב בהתחלה, ולכן אני ממליץ בחום לצפות בסרטוני ההדרכה האלו, המסבירים בביסים קטנים את ההיגיון מאחורי המבנה הזה.

ולמה זה טוב יותר?

אז מעבר ליתרונות שציינתי קודם (פחות קוד, קל לרנדר בשרת), Redux מקלה משמעותית על ביצוע Hot Swapping של רכיבי React בזמן ריצה. כלומר לאחר ששינינו את קוד הרכיב, ניתן להחליף רק אותו, ללא טעינה מחדש של כל הדף! אפשרות זאת יכולה לחסוך הרבה זמן פיתוח, במיוחד אם אנחנו נדרשים לבצע הרבה פעולות ידניות עד שמגיעים למצב שאפשר לבדוק את הרכיב.

יתרון נוסף של Redux הוא נוחות כתיבת בדיקות. מכיוון שה-Reducers הן פונקציות טהורות, הן צפויות לחלוטין ולכן קל מאוד לכתוב להן בדיקות יחידה המקבלות state ו-action, ומוודאות שה-state החדש הוא מה שציפינו. באופן זה אנו מפרידים לחלוטין את כל בדיקות ה-business logic מבדיקות ה-UI.

גם בדיקה של רכיבי ה-React עצמם הופכת לפשוטה, מכיוון שהם לא מכילים state. הבדיקות פשוט מייצרות רכיב, מוודאות שהוא מרונדר כמו שצריך, ויכולות גם לדמות פעולה (למשל לחיצת עכבר) ולוודא ששוגרו ה-Actions המתאימים.

אוקיי, אני רוצה Redux, איך מתחילים?

חשוב להבין שאין צורך לשכתב את כל האפליקציה כדי להשתמש ב-Redux (וגם ב-Flux, לצורך העניין), וניתן לכתוב רק חלק מהאפליקציה בשיטה זאת.

אני שוב ממליץ בחום לצפות בסרטוני ההדרכה, ובסופם אתם כבר אמורים לדעת איך להתחיל.

רוצים ללמוד עוד? בבקשה:

לקריאה נוספת

תהליך בנייה לקוד צד לקוח – למה צריך את זה ואיך מייצרים אחד?

במשך שנים התרגלנו לחלק את שפות התכנות לשתי קבוצות:

  • שפות מתקמפלות, כגון c ונגזרותיה, Java, ועוד. שפות אלו מצריכות תהליך הידור (קומפילציה), הממיר אותן לקוד בשפה נמוכה יותר כגון שפת מכונה או שפת ביניים, אותה ניתן להריץ ישירות על מכשירי היעד.
  • שפות סקריפט, כגון Python, PHP, Javascript וכד'. שפות אלו לא צריכות קומפילציה, והקוד שלהן נשלח כמו שהוא לשטח, שם הוא מורץ ע"י interpreter.

לאחרונה הגבול בין שתי הקבוצות החל להיטשטש. ניקח לדוגמה את פייסבוק, שם כתבו מערכת להמרת קוד ה-PHP שלהם לקוד C++ לטובת שיפור ביצועים. גם אם הקוד עדיין מפותח בשפת סקריפט, בפועל הוא מומר ומורץ בשטח כקוד בשפה אחרת. התהליך האוטומטי המבצע פעולות על הקוד כדי להכינו לפרודקשן נקרא תהליך בנייה (בִּילְד).

במאמר זה נתמקד בתהליך בנייה עבור קוד צד לקוח (Front End) הכתוב בשפות Javascript, CSS ו-HTML, נבין איך תהליך כזה עוזר לנו, ונכיר טכנולוגיות העוזרות לנו לממש אותו.

למה צריך תהליך בנייה לקוד צד לקוח?

אם קוד ה-Javascript של אפליקציית הרשת שלכם הוא קובץ אחד המכיל כמה מאות שורות בלבד, כנראה שהמאמר הזה לא בשבילכם. אך ככל שנפח הקוד שלנו גדל, והקוד הופך מורכב וקשה יותר לתחזוקה, השימוש בטכנולוגיה שתעזור לנו לתחזק את הקוד הופכת הכרחית.

בשונה משפות מתקמפלות כמו Java, תהליך הבנייה של Javascript ו-CSS לא ימיר את הקוד לשפה נמוכה יותר, אלא יבצע פעולות שיעזרו לנו להכין את הקוד לפרודקשן. קיימים הרבה סוגים של פעולות שניתן לבצע, להלן כמה מהן.

איחוד קבצי קוד

כאשר הקוד שלנו גדל, לרוב נרצה לחלק אותו למודולים כדי להקל על הפיתוח והתחזוקה. מצד שני אנחנו לא רוצים שהאתר שלנו יאלץ להוריד הרבה קבצים ויבצע לשם כך יותר מדי בקשות HTTP, זאת כדי לא להאריך את זמן הטעינה של האתר.

הפתרון הוא לחלק את הקוד לקבצים בזמן הפיתוח, ולאחד אותם (באופן אוטומטי כמובן) בתהליך הבנייה. באפליקציות קטנות ובינוניות אפשר לאחד את הקוד לקובץ אחד, ובפרויקטים גדולים מאוד ייתכן שנרצה לייצר קובץ נפרד לכל עמוד.

שתי טכנולוגיות שמבצעות עבורנו את האיחוד הזה, ועוד כמה שירותים כמו טיפול בתלויות בין מודולים הן Browserify ו-webpack.

טרנספילציה

כאשר משתמשים בשפות הנגזרות מ-Javascript כגון CoffeeScript, JSX או Dart, רצוי להמיר את הקוד ל-JS לפני ההעלאה לאתר ולא בזמן הריצה (אחרת יש צורך בטעינת ספריות js נוספות והרצתן). תהליך ההמרה, הנקרא טרנספילציה או טרנסקומפילציה, מבוצע בתהליך הבנייה, וניתן לבצע אותו גם עבור שפות המומרות ל-CSS כגון less או Sass.

סוג נוסף של טרנספילציה הוא המרת קוד המשתמש ביכולות חדשות שעדיין אינן נתמכות ע"י כל הדפדפנים לקוד המבצע את אותה פעולה אך נתמך בכולם. פרטים נוספים בפוסט הזה.

מזעור נפח הקוד

נפח קוד ה-Javascript, CSS וה-HTML שלנו משפיע רבות על זמן טעינת האתר, וכפועל יוצא גם על הדירוג שלו במנועי חיפוש. כדי לשפר נתון זה נרצה לצמצם את נפח הקוד כמה שאפשר, בלי לפגוע בנוחות הפיתוח שלנו כמובן.

כלים רבים, ביניהם Closure Compiler, UglifyJS ו-Clean-css, ממזערים את הקוד ע"י שיטות שונות כמו הסרת רווחים מיותרים, שינוי שמות משתנים מקומיים, מחיקת הערות ועוד, וזאת ללא פגיעה בתפקוד הקוד. כלים אלו גם יוודאו את תקינות הקוד ויציפו שגיאות תחביר.

לדוגמה, נניח שהקוד שלנו נראה כך:

// A simple function.
function hello(longName) {
  alert('Hello, ' + longName);
}
hello('New User');

לאחר הרצה של Closure Compiler כחלק מתהליך הבנייה, הקוד ימוזער לקוד הבא, הקצר ממנו בחצי:

function hello(a){alert("Hello, "+a)}hello("New User");

מדובר בחיסכון של 50% מנפח הקוד, שיכול להגיע למאות KB ואף יותר באפליקציות גדולות.

ניתוח סטאטי של הקוד

ישנן טעויות רבות בקוד שניתן לגלות בנקל ע"י מעבר אוטומטי על הקוד:

  • שימוש במשתנים לא מוגדרים
  • קוד שאינו בשימוש
  • קוד מת שלעולם לא יורץ
  • שימוש מסוכן באופרטורים
  • שמות משתנים ופונקציות מבלבלים ושאינם מתאימים למוסכמות

כמעט עבור כל שפות התכנות קיימים כלים המבצעים ניתוח סטאטי של הקוד ומגלים את השגיאות הנ"ל ועוד רבות אחרות. עבור JS ו-CSS ניתן להשתמש בכלים JSHint ו-CSS Lint בהתאמה.

שינוי שם קובץ הקוד הסופי

רוב האתרים משתמשים בשירות המטמון (cache) של הדפדפן כדי להאיץ את טעינת האתר. ככל שזמן שמירת המטמון ארוך יותר (ניתן לקביעה עבור כל קובץ בנפרד), כך יחסכו טעינות מיותרות של הקוד.

הבעיה מתחילה כאשר מעדכנים את הקוד של האתר. במקרה זה, הדפדפן של המשתמש עלול להמשיך לטעון את הקוד הישן מהמטמון, ולגרום לאתר שלנו לא לתפקד.

קיימות מספר שיטות לטיפול בבעיה זאת, כאשר המועדפת עליי היא קביעת שם הקובץ הסופי בזמן תהליך הבילד, כך שישתנה אם ורק אם הקוד השתנה. שיטה זאת יעילה ב-100% מכיוון שהיא תמיד תגרום לטעינה מחדש של הקוד לאחר שינוי, ומאידך מאפשרת לקבוע זמן מטמון ארוך מאוד.

כמובן שתהליך קביעת שם הקובץ מתבצע אוטומטית בתהליך הבנייה, כמו גם עדכון קבצי ה-HTML המתאימים כך שידעו איזה קובץ לטעון. הכלי שאני משתמש בו הוא gulp-rev, הקובע את שם הקובץ לפי חישוב hash על הקוד.

הרצת בדיקות אוטומטיות

במידה וכתבנו בדיקות אוטומטיות, נרצה להריץ אותן לפי העלאת הקוד לאתר.

איך מייצרים תהליך בנייה?

במידה ותהליך הבנייה שלכם פשוט ומכיל שלבים בודדים בלבד, תוכלו לממש אותו בעצמכם בשפת הסקריפט המועדפת עליכם, או אפילו ע"י shell script. אך עבור תהליכים מורכבים יותר, קיימים כלים יעודיים ומעולים שנכתבו למטרה זאת, רובם מבוססי Node.js. (שימו לב – זה לא אומר שאתם צריכים שרת Node.js כדי להריץ את האתר שלכם, אלא רק להתקין Node.js על מחשב הפיתוח). שני הכלים הנפוצים ביותר נכון לכתיבת שורות אלו הם:

Grunt

כלי להרצת משימות Javascript אחת אחרי השניה, כאשר המשימות ממומשות לרוב בפלאגינים חיצוניים. מכיוון שהשימוש ב-Grunt נפוץ מאוד, קיימים פלאגינים כמעט עבור כל משימה שתרצו להריץ.

Grunt מבוסס על קונפיגורציה, כלומר לאחר התקנת הפלאגינים הדרושים, תצטרכו להגדיר מה אתם רוצים לעשות באמצעות JSON, בקובץ JS הנקרא Gruntfile. דוגמה לקובץ Gruntfile המריץ את Uglify Js:

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
    options: {
      banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    build: {
      src: 'src/<%= pkg.name %>.js',
      dest: 'build/<%= pkg.name %>.min.js'
    }
  }
});

Gulp

בדומה ל-Grunt, גם גאלפ מתבסס על אוסף רחב של פלאגינים. אך בשונה ממנו, גאלפ מצריך כתיבת תהליך הבילד בקוד ומתבסס על Node.js streams. בפועל מדובר בקוד פשוט מאוד, שאת רובו ככולו ניתן למצוא מוכן בתיעוד הפלאגינים. לדוגמה:

gulp.task('css_lint', function() {
 return gulp.src(['src/main.less'])
   .pipe(csslint('./.csslintrc'))
   .pipe(csslint.reporter())   
   .pipe(concat(module_name + '.css'))   
   .pipe(autoprefixer());
});

אני אשאיר לכם את הבחירה בין השניים, אך אציין שהבחירה האישית שלי היא Gulp, מכיוון שהוא חדש יותר, ולדעתי התחביר שלו נוח יותר.

דוגמה לתהליך בנייה מלא

ההמלצה שלי היא לא לממש את תהליך הבנייה בבת אחת, אלא להתנסות תחילה על תהליך פשוט המבצע שתיים-שלוש פעולות. כך תוכלו ללמוד לעומק את הטכנולוגיות שבחרתם להשתמש בהן.

אם בכל זאת אתם רוצים לראות איך נראה תהליך בנייה מורכב יותר, הנה תהליך הבנייה באחד מהפרוייקטים שלי.

בנוסף למשימות שתוארו למעלה, התהליך מאפשר הרצה רציפה שלו בתהליך הפיתוח, כך שבסביבת הפיתוח אני בודק קוד זהה כמעט לחלוטין לקוד שרץ בשטח, מה שמקטין כמעט לגמרי באגים שניתנים לשחזור רק על קוד בנוי.