• בלוג
  • טעינת קבצי CSS מתוך פרויקט Webpack

טעינת קבצי CSS מתוך פרויקט Webpack

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

1. למה לטעון קבצי CSS דרך Webpack

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

  1. להפיץ ולהשתמש בספריות חיצוניות שכוללות קבצי CSS בקלות.

  2. להפעיל CSS Preprocessors באופן אוטומטי - לדוגמא להפוך קבצי Scss לקבצי CSS.

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

  4. להשתמש בטכניקות מתקדמות של שילוב CSS ו JavaScript, לדוגמא CSS Modules.

2. טעינת הקבצים ל Head למצב פיתוח

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

// in file src/main.js:

import css from './main.css';

הבעיה כמובן ש Webpack לא יודע לקרוא CSS ולכן זורק את השגיאה:

ERROR in ./src/main.css 1:5
Module parse failed: Unexpected token (1:5)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> body {
|   background: orange;
| }
 @ ./src/main.js 4:0-29

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

$ npm install --save-dev css-loader

ואז נעדכן את הגדרות ה webpack.config.js לתוכן הבא:

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'],
          },
        },
      },
      {
        test: /\.css$/,
        use: [
          { loader: 'css-loader', options: {} },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new webpack.ProvidePlugin({
      'window.jQuery': 'jquery',
    })
  ],
};

שני הקבצים הבאים בפרויקט הם הקבצים בתיקיית src, תחילה main.js שלא השתנה:

import $ from 'jquery';
import 'rateyo';
import _ from 'underscore';
import css from './main.css';

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

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

console.log(x);

ולידו באותה תיקיה הקובץ main.css שכולל רק פיסקה אחת:

body {
  background: orange;
}

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

import css from './main.css';

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

console.dir(css.toString());

בשביל לראות את האפקט של ה CSS שלנו בעמוד אנחנו עדיין צריכים לשתול את הטקסט הזה בצורה שדפדפן יוכל להבין שמדובר בעיצוב. דרך אחת קלה לעשות את זה היא לשתול תגית style ובתוכה לכתוב את תוכן הקובץ. אני יודע לעשות את זה ידנית באמצעות הקוד הבא שאפשר לכתוב בקובץ main.js:

import css from './main.css';

console.dir(css.toString());

const head = document.head;
const style = document.createElement('style');
style.textContent = css;
head.appendChild(style);

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

$ npm install --save-dev style-loader

והוספה שלו תגרום לקובץ הגדרות 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'],
          },
        },
      },
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader', options: {} },
          { loader: 'css-loader', options: {} },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new webpack.ProvidePlugin({
      'window.jQuery': 'jquery',
    })
  ],
};

3. יצירת קבצי CSS מסודרים למצב ייצור

למרות שהיה מאוד קל להשתמש ב style-loader, בפרויקטים אמיתיים נרצה להיות זהירים עם השימוש בו. הכנסה של קוד CSS לתגית style בתוך ה HTML אומרת שאי אפשר יהיה לשמור ב Browser Cache את קבצי ה CSS, ולכן כל טעינת הדפים תהיה איטית יותר. בדרך כלל נשתמש בו רק לקבצי CSS שמכילים את העיצוב המינימלי שנדרש לצורך הצגת ראש העמוד, ואת שאר ה CSS נרצה לטעון מקבצים נפרדים (אפשר להשתמש בביטויים רגולאריים שונים כדי להפריד בין שני סוגי ה CSS לפי שם הקובץ).

בשביל ליצור קבצי CSS מסודרים אנחנו משתמשים בפלאגין נוסף שנקרא mini-css-extract-plugin. אתם כבר יכולים לנחש אני מקווה איך להתקין את הפלאגין:

$ npm install --save-dev mini-css-extract-plugin

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

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-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: {} },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
    new HtmlWebpackPlugin(),
    new webpack.ProvidePlugin({
      'window.jQuery': 'jquery',
    })
  ],
};

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

התוצאה של השינוי היא יצירת קובץ main.css בתיקיית dist, והוספה של תגית link כדי לטעון אותו ל HTML שנוצר. אגב בתוך ה CSS אנחנו יכולים להשתמש בפקודות import כדי לטעון קבצי CSS אחרים לדוגמא הפקודה:

@import './lib.css';

body {
  background: orange;
}

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

4. הוספת ה CSS של RateYo

עכשיו אנחנו מוכנים לטעון גם את ה CSS של RateYo. בתיקיית הספריה אני מוצא תיקיה בשם min ושם את הקבצים:

$ ls -l node_modules/rateyo/min/
total 64
-rw-r--r--  1 ynonperek  staff    910 Aug 19 22:31 jquery.rateyo.min.css
-rw-r--r--  1 ynonperek  staff    443 Aug 19 22:31 jquery.rateyo.min.css.map
-rw-r--r--  1 ynonperek  staff   9097 Aug 19 22:31 jquery.rateyo.min.js
-rw-r--r--  1 ynonperek  staff  11129 Aug 19 22:31 jquery.rateyo.min.js.map

מזה אני מבין שבשביל ש RateYo יעבוד אני צריך להוסיף את קובץ ה CSS עם השם המפתיע jquery.rateyo.min.css. נטען אותו מתוך קובץ ה JavaScript שלנו כלומר נכתוב:

import $ from 'jquery';
import 'rateyo';
import 'rateyo/min/jquery.rateyo.min.css';
import _ from 'underscore';
import css from './main.css';


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

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

console.log(x);

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

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