מי צריך Module Bundler

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

1. מי צריך Module Bundler

בעולם הישן כל קובץ HTML כלל רשימה של קבצי JavaScript וקבצי CSS אותם אנחנו צריכים לתוכנית. דוגמא לקובץ כזה יכולה להיות:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Index</title>
  </head>
  <body>

    <h1>Hello Webpack</h1>

    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="src/utils.js"></script>
    <script src="src/my.plugin.js"></script>
    <script src="src/main.js"></script>
  </body>
</html>

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

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

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

2. התמודדות עם הבעיות של JavaScript

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

כלים אלה שמו להם למטרה לפתור את בעיית התלות בין המודולים באמצעות העברת הקוד שמחבר בין קבצי JavaScript מה HTML ל JavaScript עצמה. התחביר בו RequireJS עבד נקרא AMD והוא היווה את הבסיס למערכת התלויות בין קבצי JavaScript של Node.JS. לדוגמא ב RequireJS היינו יכולים לכתוב בתוך קובץ JavaScript קוד שנראה בערך כך:

var $ = require('jquery');

וכך לגרום ל RequireJS לטעון בשבילנו את ספריית jQuery, לתת לה את השם $ וכל זה בלי שנצטרך לציין את התלות ב HTML.

אבל אני נסחף... באנו לדבר על Webpack.

וובפאק התחיל את דרכו ב 2012, שנה אחרי ש RequireJS שוחרר לאוויר העולם. לימים כתיב AMD נזנח לטובת כתיב חדש יותר שנקרא ES6 Modules, ואתם אולי מכירים אותו באמצעות הפקודות import ו export. בקצרה כתיב המודולים מאפשר לנו להגדיר תלות מפורשת בין שני קבצי JavaScript כאשר קובץ אחד "מייצא" פונקציה או משתנה, והקובץ השני "מייבא" פונקציה או משתנה מהקובץ הראשון.

בהנחה שיש לנו קובץ בשם utils.js שמייצא פונקציה לקובץ בשם main.js, כתיב היבוא והיצוא יראה כך. תחילה הקובץ utils.js:

export function twice(x) {
    return x + x;
}

והקובץ main.js ימשוך את הפונקציה מ utils באופן הבא:

import { twice } from './utils';

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

Webpack has been a huge help to our client-side code base and developer experience in general. It's allowed greater parity and reuse between our client-side and node code, it's made testing our code much easier and it's allowed us to cut way down on the config and extra support code needed to maintain two different module systems in the same code base. The most important thing it has provided though is access to the NPM ecosystem in the browser. Coding will never be the same again :)

3. איך וובפאק פותר את כל הבעיות

וובפאק יאפשר לנו להעביר את התלות בין קבצי JavaScript מה HTML לקוד ה JavaScript עצמו. בכך הוא פותר את כל הבעיות שהוצגו קודם:

  1. לא צריך לתחזק שתי רשימות. באופן אוטומטי כל קובץ שמשתמשים בו ייטען וקובץ שלא משתמשים בו לא ייטען.

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

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

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

דברים נוספים ש Webpack יודע לעשות כוללים:

  1. וובפאק יודע להריץ קוד על קבצי המקור שלנו למשל בשביל לתרגם קוד חדש לקוד שנתמך גם בדפדפנים ישנים, או בשביל לתרגם קוד TypeScript לקוד JavaScript רגיל.

  2. וובפאק יודע לקרוא גם קבצים שנכתבו בכתיב המודולים של Node.JS וכך מאפשר לנו בקלות לשלב ספריות מ NPM אצלנו בתוכנית בקוד צד-לקוח.

4. הגדרת פרויקט וובפאק ראשון

בשביל לשלב את Webpack בצורה נוחה נרצה להוסיף לפרויקט שלנו את npm ודרכו להתקין את Webpack. אחרי שיש לכם Node.JS מותקן הפעילו מתוך תיקיה חדשה:

$ npm init -y
$ npm install --save-dev webpack webpack-cli

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

const path = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist')
  }
};

הקובץ מכיל נקודת כניסה מרכזית, במקרה שלנו הנתיב src/main.js ושם קובץ פלט, במקרה שלנו dist/app.js. בכל הפעלה של וובפאק הוא ייכנס לקובץ הראשי src/main.js, יטען דרכו את כל הקבצים שמופיעים שם ב import, ימשיך לחפש קבצים בצורה רקורסיבית לפי ה import-ים ובסוף יסדר את כל הקבצים ויכתוב אותם לתוך קובץ הפלט.

נלך לכתוב קצת קוד לפרויקט - תחילה ניצור תיקיה בשם src ובתוכה קובץ בשם main.js עם הקוד הבא:

import { twice } from './utils';
console.log('2 + 2 = ', twice(2));

נמשיך לכתוב את הקובץ utils.js בתיקיה src ובו הקוד הבא:

export function twice(x) {
    return x + x;
}

וזה מספיק בשביל לבנות את קובץ הפלט. הפעילו מתוך התיקיה הראשית:

$ npx webpack -p
Hash: 914de4260cb674080206
Version: webpack 4.39.3
Time: 330ms
Built at: 09/04/2019 4:59:40 PM
 Asset       Size  Chunks             Chunk Names
app.js  989 bytes       0  [emitted]  main
Entrypoint main = app.js
[0] ./src/main.js + 1 modules 115 bytes {0} [built]
    | ./src/main.js 68 bytes [built]
    | ./src/utils.js 47 bytes [built]

ותראו שקיבלתם קובץ בשם app.js בתוך תיקיית dist. אגב עם node אפשר כבר להריץ את הקובץ:

$ node dist/app.js
2 * 2 =  4

כדי לטעון את העמוד קובץ ה HTML שלנו צריך לטעון רק את הקובץ app.js:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Index</title>
  </head>
  <body>

    <h1>Hello Webpack</h1>

    <script src="dist/app.js"></script>
  </body>
</html>

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