שילוב וובפאק בפרויקט ווב

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

1. למה צריך Module Bundler

נתבונן בפרויקט ווב קטן שכולל קובץ html בודד ושני קבצי JavaScript. קובץ ה JavaScript הראשון מחזיק מחלקה שיוצרת אלמנט מסוג UL בעמוד ומאפשרת הוספה של אלמנטי LI בתור שורות:

class Panel {
  constructor() {
    this.el = document.createElement('ul');
    this.el.classList.add('panel');
    document.body.appendChild(this.el);
  }

  write(msg) {
    const li = document.createElement('li');
    li.textContent = msg;
    this.el.appendChild(li);
  }
}

window.panel = new Panel();

הקובץ השני בפרויקט משתמש במחלקה panel כדי לכתוב טקסט על המסך:

panel.write('hello world');

וקובץ ה HTML כולל את שני קבצי ה JS וקצת מידע מסביב:

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

    <h1>Hello Webpack</h1>

    <script src="src/panel.js"></script>
    <script src="src/main.js"></script>
  </body>
</html>

וכבר ב-3 קבצים של קוד אנחנו יכולים לראות די הרבה בעיות במבנה הפרויקט:

  1. בקובץ main.js אנחנו מתיחסים למשתנה panel, שהוגדר בקובץ אחר. קל מאוד בטעות לשנות את שם המשתנה בקובץ האחר וכך לשבור את הפרויקט.

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

  3. בקובץ index.html אנחנו צריכים לכלול את שני קבצי ה JavaScript ולהקפיד על הסדר ביניהם. אם בטעות נשנה את הסדר ונטען את main.js לפני panel.js הכל יישבר.

תפקידו של Module Bundler להעביר את ניהול התלויות לקוד JavaScript ובצורה מפורשת כך ש:

  1. קובץ JavaScript יציין בתוך גוף הקובץ עצמו באיזה קבצי JavaScript אחרים הוא משתמש.

  2. קובץ JavaScript יקבע לעצמו איזה חלקים ממנו נועדו כדי שאחרים יוכלו להשתמש בהם.

  3. כל קובץ JavaScript שמשתמש ברכיבים מקבצים אחרים יקבע לעצמו איך לקרוא לרכיבים האלה, כך ששינוי בקובץ האחר לא ישבור את הקוד.

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

  1. טעינה של קבצי CSS כך שקובץ JavaScript יוכל לציין שהוא משתמש בקבצי CSS מסוימים, ואז לא נצטרך לכלול אותם ב HTML.

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

  3. הגדרת חלקים מסוימים מה JavaScript שייטענו מאוחר יותר באמצעות Ajax ובכך טעינת הדף הראשונית תסתיים מהר יותר.

  4. הפעלה אוטומטית של כלים על קבצי ה CSS שלנו, לדוגמא עבור תמיכה ב Scss.

ויכולות רבות נוספות עליהן נלמד בסידרת מדריכים זו.

2. שילוב npm ו Webpack בפרויקט

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

$ npm init

מתיקיית הפרויקט הראשית כדי ליצור את הקובץ package.json בתיקיה. הפקודה תשאל אתכם כמה שאלות ואפשר פשוט ללחוץ Enter כל כולן.

לאחר מכן התקינו את Webpack ואת כלי שורת הפקודה שלו באמצעות:

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

ולסיום וודאו ש Webpack עובד מתוך הפרויקט באמצעות:

$ npx webpack --version

אם הכל הלך כמו שצריך תקבלו על המסך את גירסת הוובפאק, אצלי התוצאה היא:

4.39.2

הצעד הבא הוא ליצור קובץ הגדרות ראשוני ל Webpack. במבנה הפרויקט שלי שני קבצי ה JavaScript נמצאים בתיקית src וקובץ ה index.html נמצא בתיקיה הראשית של הפרויקט. קובץ ההגדרות הוא זה שמספר ל Webpack על המבנה שבחרתי. שם הקובץ הוא 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, לבנות את כל עץ הפרויקט מקבצי ה JavaScript ולקבל קובץ בשם app.js שישמר בתיקיה בשם dist.

לסיום נעדכן את הקובץ package.json כך שיכלול שני קיצורי דרך להפעלת Webpack:

{
  "name": "after",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack -p"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^4.39.2",
    "webpack-cli": "^3.3.6"
  }
}

הוספתי בתוך האוביקט scripts את קיצור הדרך build שבונה את קבצי הפרויקט באמצעות הפעלת וובפאק במצב ייצור.

3. התאמת קוד הפרויקט לעבודה עם Webpack

כל העבודה הקשה שעשינו עד עכשיו מתחילה לתת תוצאות כשאנחנו מגיעים לעדכון קבצי המקור של הפרויקט למבנה החדש. נתחיל עם הקובץ src/panel.js:

class Panel {
  constructor() {
    this.el = document.createElement('ul');
    this.el.classList.add('panel');
    document.body.appendChild(this.el);
  }

  write(msg) {
    const li = document.createElement('li');
    li.textContent = msg;
    this.el.appendChild(li);
  }
}

const panel = new Panel();
export default panel;

במקום לשמור את המשתנה panel כשדה מידע חדש על window, השתמשתי בפקודה export default. פקודה זו מעבירה ל Webpack את האחריות על חיבור משתנה זה לקבצים אחרים שיצטרכו אותו. במקום להשתמש במשתנה ישירות, כל קובץ אחר שירצה להשתמש ב panel יצטרך "לייבא" אותו בצורה מפורשת באמצעות פקודת import, בתוכה אפשר יהיה לקבוע מה יהיה שם המשתנה בתוך הקובץ שצריך אותו.

אני חושב שזה יהיה יותר קל לראות את זה כשנסתכל בקוד המעודכן של הקובץ src/main.js:

import panel from './panel';

panel.write('hello world');

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

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

השינוי האחרון הוא בקובץ index.html, לאחריו הקובץ יראה כך:

<!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>

כאן במקום לטעון את כל קבצי ה JavaScript אני טוען רק קובץ אחד שלמעשה עדיין לא קיים, הקובץ dist/app.js. זה הקובץ שהגדרתי בתור output בקובץ ההגדרות של webpack, כלומר זה הקובץ ש webpack יצור מתוך קבצי המקור שלי. באופן אוטומטי וובפאק יצליח ליצור קובץ אחד ולכתוב שם את השורות לפי הסדר הנכון כך שאין חשש להתבלבל ולשבור את הקוד בגלל סדר לא נכון של קבצי ה JavaScript.

4. הפעלת וובפאק ועבודה במצב פיתוח

נפעיל משורת הפקודה את הקוד:

$ npm run build

והפלט אצלי נראה כך:

> after@1.0.0 build /Users/ynonperek/work/courses/web/webpack/demos/01-first-webpack-project/after
> webpack -p

Hash: 0c0b231caf0463bcfdb0
Version: webpack 4.39.2
Time: 128ms
Built at: 08/14/2019 11:39:01 AM
 Asset      Size  Chunks             Chunk Names
app.js  1.16 KiB       0  [emitted]  main
Entrypoint main = app.js
[0] ./src/main.js + 1 modules 387 bytes {0} [built]
    | ./src/main.js 58 bytes [built]
    | ./src/panel.js 329 bytes [built]

אנחנו רואים ש Webpack יצר את הקובץ app.js מתוך שני הקבצים src/main.js ו src/panel.js. אם נפתח בדפדפן את הקובץ index.html נוכל לראות שהתוכנית שלנו עדיין עובדת וכותבת למסך.

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

נעדכן את הקובץ package.json כדי להוסיף תמיכה למצב פיתוח:

{
  "name": "after",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack -d --watch",
    "build": "webpack -p"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "webpack": "^4.39.2",
    "webpack-cli": "^3.3.6"
  }
}

ועכשיו משורת הפקודה אפשר להפעיל:

$ npm run dev

בתגובה Webpack לא יחזור לשורת הפקודה וימשיך להסתכל על הקבצים שלנו. בכל שינוי באחד מקבצי המקור באופן אוטומטי וובפאק יבנה את הקובץ dist/app.js מחדש. נסו את זה אצלכם ותראו שמספיק לשנות את אחד מקבצי המקור, לשמור ולרענן את הדף בדפדפן כדי לראות את השינוי. בשביל לסיים לוחצים מה CMD על כפתור Ctrl+C.

5. סיכום

בשביל להוסיף את Webpack לפרויקט נוח לנו להשתמש ב npm כדי להתקין את הכלי וליצור מספר קיצורי דרך להפעלה מהירה שלו. לאחר מכן אנחנו יוצרים קובץ הגדרות בשם webpack.config.js ואז יכולים לעדכן את קוד הפרויקט לעבודה בשיטה החדשה - בה קבצי JavaScript (שנקראים מודולים) יכולים לטעון קבצי JavaScript אחרים.

כלי נחמד שיעזור לכם במהלך הקורס ובמשחקים עם וובפאק בהמשך נקרא createapp.dev. הכלי מאפשר להוסיף ולהוריד פיצ'רים לוובפאק באמצעות GUI עם תפריטים.