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

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

  • שפות מתקמפלות, כגון 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, מכיוון שהוא חדש יותר, ולדעתי התחביר שלו נוח יותר.

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

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

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

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