סקירת הספריה emotion.js

13/09/2018

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

1. מי צריך לכתוב CSS ב JavaScript

בשנים האחרונות מתכנתים עוברים לכתוב קוד ל Web Applications במבנה של פקדים: אנחנו כותבים חתיכת קוד שאמורה לייצר אלמנט מסוים בממשק ומשתמשים בה במספר מקומות (עם ההתאמות הנדרשות) כדי לבנות דפים יותר גדולים. אנו מכנים כל אלמנט כזה בממשק בשם פקד, או Component באנגלית. דוגמאות לאלמנטים כאלה כוללות "פרק בפוסט" כאן בבלוג, או את נגן הוידאו אבל גם את התפריט העליון באתר, תפריט צד, פוטר, תיבת חיפוש ועוד. הייתרון בחשיבה על האתר שלנו בתור אוסף של פקדים הוא שאפשר לבנות קוד מאוד מתוחכם לכל פקד בנפרד משאר האתר.

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

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

class Button extends React.Component {
  render() {
    return (
      <button className='btn'>Click Me</button>
    )
  }
}

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

class ImageGallery extends React.Component {
  render() {
    return (
      <div>
        <h1>The Images</h1>
        <button className='btn'>Load More ...</button>
        <Images />
      </div>
    );
  }
}

והנה יצרנו חיבור בין שני פקדים שונים שאולי יושבים בשני קבצים שונים ובמקומות אחרים לגמרי במערכת. שנו את הגדרות ה CSS של פקד אחד ותקבלו השפעה שאולי לא התכוונתם אליה בפקד אחר.

הפיתרון הראשון שאנשים חשבו עליו לבעיה זו הוא להשתמש בשמות מקוריים ל CSS Classes שלנו. אפשר למשל להוסיף לכל קלאס את שם הפקד שמשתמש בו ואז לקבל:

class ImageGallery extends React.Component {
  render() {
    return (
      <div>
        <h1>The Images</h1>
        <button className='image-gallery-btn'>Load More ...</button>
        <Images />
      </div>
    );
  }
}

class Button extends React.Component {
  render() {
    return (
      <button className='button-btn'>Click Me</button>
    )
  }
}

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

2. שלום Emotion

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

$ npm install --save emotion react-emotion babel-plugin-emotion

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

import React from "react";
import ReactDOM from "react-dom";
import { css } from 'emotion';

const baseButton = css({
  'padding': '10px',
  'background': 'yellow',
  'font-size': '24px',
});

const imageGalleryButton = css(
  baseButton,
  {
    color: 'black',
  }
);

class ImageGallery extends React.Component {
  render() {
    return (
      <div>
        <h1>The Images</h1>
        <button className={imageGalleryButton}>Load More ...</button>
      </div>
    );
  }
}


const mainButton = css(
  baseButton,
  {
    color: 'red',
  }
);
class Button extends React.Component {
  render() {
    return (
      <button className={mainButton}>Click Me</button>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <ImageGallery />
        <Button />
      </div>
    );
  }
}

var mountNode = document.getElementById("app");
ReactDOM.render(<App />, mountNode);

התוכנית כוללת שני שינויים מרכזיים לעומת הגישה של כתיבת ה CSS ב CSS:

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

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

כשנתבונן באלמנט בכלי הפיתוח נוכל לראות תופעה מעניינת:

<button class="css-gs6mfs">Load More ...</button>

ה css class שנבחר עבורנו נקרא css-gs6mfs. זה לא משהו שאני בחרתי או שיש לי איזושהי שליטה עליו (למעשה השם נגזר מחישוב Hash Value על כללי ה CSS עצמם).

3. הפונקציה css

הפונקציה css של emotion יוצרת כלל CSS חדש. היצירה מתרחשת בצורה דינמית בדפדפן אחרי טעינת העמוד וההגדרות נשמרות בתוך תגית style בחלק ה HEAD של הדף. כך נראה ה head של העמוד שכתבתי:

<head>
        <title>React starter app</title>
    <style data-emotion=""></style><style data-emotion="">.css-1kg9h4{padding:10px;background:yellow;font-size:24px;}</style><style data-emotion="">.css-gs6mfs{padding:10px;background:yellow;font-size:24px;color:black;}</style><style data-emotion="">.css-1p1o1r8{padding:10px;background:yellow;font-size:24px;color:red;}</style></head>

ניתן לראות שהוגדרו 3 קלאסים מאחר והפעלתי את הפונקציה css שלוש פעמים.

4. יצירת אלמנט מעוצב חדש

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

import React from "react";
import ReactDOM from "react-dom";
import { css } from 'emotion';
import styled from 'react-emotion'

const Square = styled.div({
  display: 'inline-block',
  background: 'red',
  width: '100px',
  height: '100px', 
  margin: '10px',
  ':hover': {
    background: 'pink',
  }
});

class App extends React.Component {
  render() {
    return (
      <div>
        <Square />
        <Square />
        <Square />
      </div>
    );
  }
}

var mountNode = document.getElementById("app");
ReactDOM.render(<App />, mountNode);

האלמנט Square הוא פקד React שנוצר מהפונקציה styled ובאופן אוטומטי כולל div עם קלאס שמחזיק את כל המאפיינים בהגדרות ה CSS שכתבתי. כך נראה אלמנט כזה בכלי הפיתוח של כרום:

<div class="css-7ymqkt ekk75sp0"></div>

ואלה הגדרות ה CSS המתאימות:

.css-7ymqkt:hover {
    background: pink;
}

localhost:1.css-7ymqkt {
    display: inline-block;
    background: red;
    width: 100px;
    height: 100px;
    margin: 10px;
}

שנוצרו באופן אוטומטי על ידי הספריה emotion.js.

5. חיבור עם ריאקט

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

const ColorSquare = styled.div(props => ({
  display: 'inline-block',
  background: props.color || 'red',
  width: '100px',
  height: '100px', 
  margin: '10px',
  ':hover': {
    background: 'pink',
  }
}));

class App extends React.Component {
  render() {
    return (
      <div>
        <ColorSquare color='orange' />
      </div>
    );
  }
}

וכך יצרנו אלמנט בשם ColorSquare שמאפיין color שלו קובע מה יהיה צבע הרקע של המשבצת.

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

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

https://github.com/tocodeil/emotion-demo

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

$ npm start

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

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