הפלאגין HTML Webpack Plugin

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

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

1. שליטה על קובץ ה HTML שנוצר

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

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

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Template</title>
  </head>
  <body>
    <h1>Hello Webpack HTML Plugin</h1>
    <p>I can use any HTML file I want</p>
    <p>AND it will automatically use lodash template engine so this works:
    <pre>1 + 1 = <%= 1 + 1 %></pre>
    </p>
  </body>
</html>

נעדכן את הקטע הרלוונטי בהגדרת webpack.config.js כך שיכיל התיחסות לטמפלייט כלומר:

    new HtmlWebpackPlugin({
      template: './html/template.html',
    }),

והקובץ webpack.config.js המלא (בשביל קופי-פייסט) נראה כך:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPartialsPlugin = require('html-webpack-partials-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'],
          },
        },
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: process.env.NODE_ENV === 'development',
            },
          },
          { loader: 'css-loader', options: {} },
        ],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              outputPath: 'images',
            }
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
    new HtmlWebpackPlugin({
      template: './html/template.html',
    }),
    new webpack.ProvidePlugin({
      'window.jQuery': 'jquery',
    })
  ],
};

מעניין לשים לב שאפשר להשתמש בסימן <%= ... %> כדי לכתוב קוד שיפוענח כמו קוד JavaScript וידביק את התוצאה לתוך קובץ ה HTML, ממש כמו בסביבות תבניות בצד שרת.

2. טיפול בתמונות

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

בהנחה שיש לי בפרויקט תיקיה בשם img ובתוכה קובץ בשם puppy1.jpg, אוכל למשל להוסיף את השורה הבאה לתבנית ה HTML שלי:

    <img src="<%= require('../img/puppy1.jpg') %>" />

ובקובץ ה HTML שייווצר בתיקיית dist אקבל במקומה את השורה:

    <img src="images/b96d6c6f4296f559ec55289fa67143c4.jpg" />

3. יצירת מספר קבצי HTML בפרויקט

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

תחילה ניצור קובץ JavaScript חדש בשם about.js שיכיל רק את הקוד הבא שיופעל ממסך ה about:

console.log('About Page');

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

  entry: {
    main: './src/main.js',
    about: './src/about.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },

השינוי בשדה output היה נחוץ כדי ששם קובץ הפלט יהיה מושפע משם קובץ הקלט: הקובץ main.js יהפוך לקובץ main.js בתיקיית dist, והקובץ about.js יהפוך ל dist/about.js.

כרגע עדיין יש לנו רק קובץ index.html יחיד בתיקיית dist. נתקן את זה באמצעות הוספת הפלאגין HTML Webpack Plugin פעם שניה לפרויקט:

  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
    new HtmlWebpackPlugin({
      template: './html/template.html',
    }),
    new HtmlWebpackPlugin({
      template: './html/about.html',
      filename: 'about.html',
    }),
  ],

ניצור את הקובץ html/about.html עם התוכן הבא:

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

ועכשיו כשנבנה את הפרויקט עם:

$ npx webpack -p

נקבל בתיקיית dist שני קבצי HTML.

אבל עדיין יש בעיה - כל אחד מקבצי ה HTML טוען את שני קבצי ה JavaScript, במקום שכל קובץ יטען רק את קבצי ה JavaScript הרלוונטים עבורו. תשמחו לשמוע שב HTML Webpack Plugin כבר חשבו על הבעיה ויש פיתרון פשוט שנקרא Chunk Filtering. בעברית זה אומר שאנחנו יכולים לבקש רק קבצי JavaScript מסוימים בתוך כל אחד מקבצי ה HTML שלנו. ההגדרות נראות כך:

  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
    new HtmlWebpackPlugin({
      template: './html/template.html',
      chunks: ['main'],
    }),
    new HtmlWebpackPlugin({
      template: './html/about.html',
      filename: 'about.html',
      chunks: ['about'],
    }),
  ],

4. שיתוף קוד בין מספר קבצי HTML בפרויקט

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

 <%= require('html-loader!./partial.html') %>

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

<p>Created By Ynon Perek</p>

נוסיף לכל אחת משתי התבניות (כלומר ל html/about.html ול html/template.html) את הקישור לקובץ html/footer.html החדש שלנו באמצעות הוספת השורה הבאה לכל אחת מהתבניות:

 <%= require('html-loader!./footer.html') %>

לפני שנמשיך אל תשכחו להתקין את html-loader עם:

$ npm install --save-dev html-loader

ובפעם הבאה שתבנו את הפרויקט בתחתית כל אחד משני קבצי ה HTML נקבל את אותו Footer.