שימוש בספריות חיצוניות בפרויקט Webpack

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

1. התקנת ספריה חיצונית מ npm

מאגר החבילות npm הפך בשנים האחרונות לבית של אינספור ספריות פיתוח צד-לקוח, בין השאר נוכל למצוא בו את jQuery, React, underscore, ואפילו ספריות CSS כמו Bootstrap כבר נמצאות שם. קחו לכם משחק של כמה דקות וכנסו לדף החיפוש.

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

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

$ npm install --save-dev underscore
$ npm install --save-dev jquery
$ npm install --save-dev rateyo

2. הגדרות Babel כך שלא יפעל על ספריות חיצוניות

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

בקובץ הגדרות ה Webpack הפיסקה הבאה אחראית על הפעלת Babel ודילוג על כל הקבצים מ node_modules:

      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },

חשוב לוודא שיש לכם את שורת ה exclude כדי לא להריץ Babel על אותן הספריות שהגיעו כבר מוכנות מ npm.

3. שימוש בספריה חיצונית בתוך קוד הפרויקט

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

  1. אנחנו יכולים לטעון את קבצי ה JavaScript שלהן ישירות דרך Webpack באמצעות import.

  2. אנחנו יכולים לטעון את קבצי ה JavaScript כמשתנים גלובאליים באמצעות ProvidePlugin.

  3. אנחנו יכולים לטעון Assets אחרים מהחבילות (לדוגמא קבצי CSS) באמצעות Webpack.

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

4. שימוש בספריה באמצעות import

ספריות חיצוניות רבות מ npm כבר כוללות את תחביר ה export שראינו ולכן אפשר פשוט לבצע import מהן, והן בתורן עושות import לספריות אחרות שהן צריכות. אלה הספריות שיהיה לנו הכי קל לעבוד איתן.

לדוגמא לספריה underscore אין שום תלויות והיא עושה export למשתנה הראשי של הספריה כך שאפשר בקלות לבצע import לספריית underscore ולהשתמש בה מתוך קובץ ה JavaScript שלנו. בשביל הדוגמא ניצור פרויקט עם קובץ קוד יחיד בשם src/main.js ועם התוכן הבא:

import _ from 'underscore';

console.log(_.random(0, 100));

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

$ ls -l node_modules/underscore/
total 248
-rw-r--r--  1 ynonperek  staff   1117 May 31  2018 LICENSE
-rw-r--r--  1 ynonperek  staff   1515 Apr 18  2018 README.md
-rw-r--r--  1 ynonperek  staff   2424 Aug 16 18:52 package.json
-rw-r--r--  1 ynonperek  staff  18069 Jun  1  2018 underscore-min.js
-rw-r--r--  1 ynonperek  staff  30472 Jun  1  2018 underscore-min.js.map
-rw-r--r--  1 ynonperek  staff  58319 Jun  1  2018 underscore.js

בגלל שבספריה יש קובץ בשם package.json באופן אוטומטי וובפאק יסתכל בתוכו, ימצא שם את השורה:

"main": "underscore.js"

וכך יבין שהוא צריך לטעון את הקובץ underscore.js.

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

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

npm install --save-dev webpack webpack-cli babel-loader @babel/core @babel/preset-env html-webpack-plugin underscore

הספריה היחידה ברשימה שעשויה להפתיע היא html-webpack-plugin. ספריה זו תיצור בשבילנו באופן אוטומטי קובץ HTML שיטען את קובץ ה JS. בסוף קריאת הפוסט אולי תרצו להעיף מבט בתיעוד הספריה כי יש די הרבה דברים שהיא יודעת לעשות. אנחנו נשתמש כאן רק בבסיס.

בחזרה לפרויקט וכעת ניצור קובץ webpack.config.js עם התוכן הבא:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin(),
  ],
};

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

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack -d -w",
    "build": "webpack -p"
  },

ואפשר לצאת לדרך. בנו את הפרויקט עם:

npm run build

ועכשיו יש לכם קובץ בשם dist/index.html שבאופן אוטומטי יטען את קובץ ה JavaScript ששמו app.js גם הוא מתיקיית dist. פתחו את הקובץ כדי לראות בקונסול הדפסה של מספר אקראי.

5. הגדרה גלובאלית של משתנים מספריה חיצונית

לא כל הספריות שנרצה לטעון משתמשים בכתיב ה import/export. לדוגמא הספריה rateyo שהזכרתי בתחילת הפוסט היא ספריה ישנה שלא משתמשת בכתיב זה. נתקין אותה ונראה איך לטעון אותה למרות המבנה הישן. תחילה ההתקנה עדיין דרך npm:

$ npm install --save-dev rateyo jquery

מבנה הספריה הוא מבנה קלאסי של jQuery Plugin. כלומר קוד הספריה בנוי בתוך פונקציה כזו:

;(function($) {
  // ...
}(window.jQuery));

וזו בעיה - כי כשאנחנו טוענים קובץ כזה מתוך Webpack אין לו את window.jQuery. בשביל לגשת ל jQuery הוא היה צריך לעשות import. בנוסף הקובץ לא עושה export לשום דבר אז לא לגמרי ברור איך צריך לטעון אותו.

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

import 'rateyo';

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

jquery.rateyo.min.js?9781:3 Uncaught TypeError: Cannot read property 'fn' of undefined
    at eval (jquery.rateyo.min.js?9781:3)
    at eval (jquery.rateyo.min.js?9781:3)
    at Object../node_modules/rateyo/min/jquery.rateyo.min.js (app.js:96)
    at __webpack_require__ (app.js:20)
    at eval (main.js?56d7:1)
    at Module../src/main.js (app.js:141)
    at __webpack_require__ (app.js:20)
    at app.js:84
    at app.js:87

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

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

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

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new webpack.ProvidePlugin({
      'window.jQuery': 'jquery',
    })
  ],
};

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

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

import $ from 'jquery';
import 'rateyo';
import _ from 'underscore';

let x = _.random(0, 100);

$('body').append('<div id="rater"></div>');
$('#rater').rateYo();

console.log(x);

נשארה לנו רק בעיה אחת - ה CSS של rateYo לא נטען כי HtmlWebpackPlugin לא מכיר אותו. בפוסט הבא בסידרה נלמד על טעינת קבצי CSS מתוך Webpack כדי לפתור בעיה זו ולקבל הרבה פינוקים חדשים.