רשימת המילים בעברית בפחות מ-50 שורות קוד

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

שניה לפני שהתייאשתי נזכרתי שאני מתכנת והחלטתי לנצל את ההזדמנות כדי לייצר רשימה כזאת בעצמי, ואתם הרווחתם עוד פוסט :)

איך נייצר את הרשימה?

אפשר לחשוב על הרבה שיטות למציאת רשימת כל המילים, והשיטה שאציג בפוסט זה היא מיצוי כל המילים מתוך טקסט ארוך מאוד. כמה ארוך? ובכן מכיוון שאין לי צורך ב-100% מהמילים הקיימות בשפה העברית, וגם לא בתדירות השימוש המדוייקת שלהן, נסתפק ברשימת כל המילים המופיעות בכל הערכים ב-ויקיפדיה העברית. נכון לכתיבת שורות אלו, מדובר במעל 200,000 ערכים (עשרות מיליוני מילים).

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

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

החומרים הדרושים

כדי לקרוא קוד HTML נשתמש בספרייה BeautifulSoup, ובספריית re הסטנדרטית של python. המחלקה Counter מהמודול collections תשמש לשמירת מיפוי בין כל מילה למספר ההופעות שלה, והספרייה progressbar2 תשמש ליצירת מד התקדמות.

from bs4 import BeautifulSoup
from collections import Counter
from progressbar import ProgressBar
import re, os

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

שלב ראשון – יצירת רשימה של שמות הקבצים

נתחיל בחילוץ (unzipping) הקובץ שהורדנו, המכיל את כל הערכים בויקיפדיה, ע"י שימוש בתוכנה 7Zip. התוצאה היא עץ תיקיות ענקי המחולק לתיקיות לפי האות הראשונה של שם הערך, לאחר מכן האות השנייה וכו'. נרצה לעבור על כל הקבצים בעץ, ולשמור את הנתיבים שלהם ברשימה אחת ארוכה.

נעשה זאת ע"י שימוש בפונקציה walk של מודול os, המקבל תיקייה, ומחזירה איטרטור (iterator) על כל הקבצים והתיקיות בה בצורה רקורסיבית:

all_files = []
for root, folders, filenames in os.walk(u'wikipedia-he-html'):
  for filename in filenames:
    all_files.append(os.path.join(root, filename))

שימו לב שאנו מעבירים לפונקציה את שם התיקייה בתור מחרוזת unicode (מתחילה באות u). דבר זה נדרש כדי שהפונקציה תתמוך בנתיבים המכילים עברית.

כל ערך החוזר מהאיטרטור הוא שלשה המייצגת תיקייה, שמות כל תתי התיקיות בה, ושמות כל הקבצים בה. נרוץ בלולאה על שמות הקבצים ונוסיף אותם לרשימה, לא לפני שנחבר אותם לנתיב המלא של הקובץ (המורכב מנתיב התיקייה + שם הקובץ), ע"י שימוש בפונקציה join במודול os.path.

נסו להדפיס את אורך הרשימה all_files כדי לוודא שהיא אכן ארוכה מאוד.

שלב שני – קריאת תוכן הקבצים

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

with ProgressBar(max_value = len(all_files)) as progress:
  for i, file_path in enumerate(all_files):
    progress.update(i)
    html = open(file_path, "rb").read().decode('utf8')

שימו לב שקריאת הקובץ מורכבת משלושה שלבים:

  1. פתיחת הקובץ לקריאה באמצעות הפונקציה open. בחלק ממערכות ההפעלה (בעיקר Windows) חשוב להעביר את הפרמטר "rb" (שמשמעותו read binary) כדי שהקובץ ייקרא בדיוק כמו שהוא.
  2. קריאת תוכן הקובץ לתוך מחרוזת ע"י הפונקציה read.
  3. פתיחת הקידוד של הקובץ, כלומר המרת תוכנו מפורמט בינארי בקידוד UTF-8 למחרוזת מסוג unicode, ע"י הפונקציה decode.

אם שאלתם את עצמכם איך אנחנו יודעים שקידוד הקובץ הוא UTF-8, התשובות הן: (א) זהו הקידוד הנפוץ ביותר לדפי HTML; (ב) כמעט כל עורך טקסט מתקדם יזהה זאת עבורכם כשתפתחו באמצעותו את אחד הקבצים; (ג) זה כתוב בתוך הקבצים: charset=UTF-8.

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

all_files = all_files[:1000]

כעת נסו להריץ את הקוד, סביר להניח שקיבלתם את השגיאה הבאה:

UnicodeDecodeError: 'utf8' codec can't decode byte 0x89 in position 0: invalid start byte

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

try:
  html = open(file_path, "rb").read().decode('utf8')
except UnicodeDecodeError:
  continue

לאחר סיום שלב זה יש לנו, עבור כל קובץ, מחרוזת עם התוכן שלו, שהוא קוד HTML.

שלב שלישי – מציאת כל הטקסט בעמוד

מכיוון שכל הקבצים הגיעו ממקור יחיד – המבנה שלהם דומה, ובפרט כל הטקסט שאנו מחפשים מופיע בתוך תגיות HTML מסוג <p> (פסקה). נרצה לעבור על התוכן של כל אחת מהתגיות הללו.

הדרך הפשוטה ביותר היא שימוש בספרייה BeautifulSoup הנועדה לפענוח קוד HTML:

soup = BeautifulSoup(html, 'html.parser')
for p in soup.find_all('p'):
  # do something with p

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

דרך חלופית ומהירה הרבה יותר היא מציאת כל התגיות <p> בקובץ ע"י שימוש בביטויים רגולרים (regular expressions), ופענוח התוכן שלהן בלבד:

PARAGRAPHS_PATTERN = re.compile(r"<p>(.*?)</p>")

for p_html in PARAGRAPHS_PATTERN.findall(html):
  p = BeautifulSoup(p_html, 'html.parser')

נתעכב על הביטוי הרגולרי: הוא מתחיל בתגית הפותחת <p> ומסתיים בתגית הסוגרת </p>. תוכן התגית מותאם (matched) ע"י הביטוי הנפוץ נקודה-כוכבית, והוא בתוך סוגריים מכיוון שרק בו אנו מעוניינים, ללא תגיות הפסקה. אך מה פשר סימן השאלה?

נניח שיש לנו שתי פסקאות בעמוד:

<p>hello from icode.co.il</p>
<p>don't forget to subscribe</p>

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

<p>hello from icode</p>
<p>don't forget to subscribe</p>

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

<p>hello from icode</p>
<p>don't forget to subscribe</p>

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

  1. פעמים רבות התגית p תכיל תכונות (attributes) שונות, למשל <p class="blue">, ולכן לא תיתפס ע"י הביטוי הרגולרי. בויקיפדיה תגיות הפסקה לא מכילות תכונות.
  2. במידה ותגית p נמצאת בתוך תגית p אחרת, הביטוי הרגולרי יחזיר תוצאה שגויה. אמנם הדבר לא חוקי בקוד HTML, אך אתרים רבים עדיין מבצעים את הטעות הזאת, והדפדפנים סלחנים כלפיה. למזלנו בויקיפדיה קוד ה-HTML הוא תקני (ואף מיוצר אוטומטית), ולכן הבעיה הזאת לא קיימת.

בשלב זה המשתנה p מכיל אובייקט מטיפוס Tag (של BeautifulSoup), המכיל ייצוג כלשהו של תוכן הפסקה. אך זה עדיין לא מספיק, מכיוון שהייצוג הזה עלול להכיל בעצמו קוד HTML:

<p>hello from <a href="/">icode</a><p>

למזלנו, BeautifulSoup מספקת פונקציה פשוטה להמרת קוד HTML לטקסט:

p_text = p.get_text() # => "hello from icode"

שלב רביעי ואחרון – חלוקה למילים וספירתן

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

words = p_text.split()

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

CHARS_PATTERN = re.compile(ur"""[^אבגדהוזחטיכלמנסעפצקרשתןףץםך'\- "]""")
p_text = CHARS_PATTERN.sub('', p_text)

השורה הראשונה מגדירה ביטוי רגולרי התופס כל תו שאינו אות, גרש, גרשיים, מקף, או תו רווח כלשהו (whitespace). הסיבה שאנחנו כן רוצים חלק מהתווים המיוחדים היא שהם יכולים להופיע כחלק ממילה (מכ"ם, ג'ורדן) או ביטוי (עלגבי). כמובן שניתן להחליט להריץ את הקוד ללא הסימנים האלו, או חלקם, ולקבל תוצאות מעט שונות.

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

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

שימו לב גם שהקוד שלנו מכיל תווים בעברית. כדי שנוכל להריץ קוד כזה, עלינו להקפיד על מספר דברים:

  • קובץ הקוד שלנו צריך להיות שמור בקידוד UTF-8. רוב עורכי הטקסט תומכים בשמירה בקידוד זה.
  • מחרוזות המכילות עברית חייבות להתחיל בתו u (מסמל unicode) לפני המחרוזת.
  • יש להוסיף בתחילת הקובץ את ההערה הבאה:
# -*- coding: utf-8 -*-

כעט נשתמש במבנה הנתונים Counter כדי לספור כמה מופעים יש מכל מילה:

freq = Counter()

for word in words:
  word = word.strip('="')
  if len(word) > 1:
    freq[word] += 1

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

  1. משתמשים בפונקציה strip כדי להסיר מקפים וגרשיים המופיעים בתחילתה או בסופה (למשל המילה "ב-" תהפוך למילה "ב").
  2. מוודאים שהמילה היא באורך שני תווים לפחות, כדי לדלג על אותיות קישור המופרדות במקף.
  3. מגדילים באחד את מספר ההופעות ב-Counter שלנו (במידה ואין עדיין הופעות, Counter דואג לאתחל אוטומטית את מספר ההופעות ל-0).

סיימנו! בסיום הריצה המשתנה freq הוא Counter (סוג של מילון) שמפתחותיו הן כל המילים, וערכיו הם מספר ההופעות של כל מילה בכל הטקסט. כל שנשאר הוא לשמור לקובץ בפורמט לבחירתנו, למשל:

open('words.txt', "wb").write(
  u"\n".join("%s, %s" % x for x in freq.most_common()).encode('utf8'))

חשוב לזכור לקודד כ-UTF-8 (או קידוד אחר התומך בעברית) טרם השמירה.

קובץ המילים אמור להיראות כך:

של, 523462
את, 362830
על, 280208
הוא, 155791
לא, 122371
...
פילי, 44
הלשכות, 44
שליחו, 43
בבואה, 43
...

העשרה: שיפור מהירות הריצה

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

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

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

נסו לעבור על הקוד המלא ולמצוא אותן.

מה הלאה?

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

  1. זיהוי והסרת אותיות קישור מהמילים. למשל המילה "במכונית" תיספר כמו המילה "מכונית"
  2. הסרת מילים שתדירותן נמוכה מדי
  3. שימוש במקור טקסט אחר
  4. השוואה בין מקורות טקסט שונים ליצירת רשימת מילים שאינה מוטה למקור מסויים
  5. חיפוש ביטויים נפוצים במקום מילים נפוצות
  6. תמיכה בשפות נוספות
  7. שימוש ב-dump עדכני יותר של ויקיפדיה (קיים בפורמטים שאינם HTML)

הקוד המלא

נמצא כאן. כמו תמיד, אתם מוזמנים לשלוח הערות והצעות לשיפור.

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

בשנים האחרונות הפך הטרנד של "עיצוב שטוח" (flat design) לנפוץ במיוחד. אחד המאפיינים הבולטים של גישה עיצובית זו, בעיקר באפליקציות web, הוא השימוש באייקונים מונוכרומטיים (בעלי צבע אחד). דוגמה שכרגע נמצאת לי מול העיניים היא מערכת הניהול של wordpress:

Flat icons
אייקונים מונוכרומטיים

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

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

SVG – תמונה וקטורית

Smart Vector Graphics, או SVG הוא פורמט תמונה וקטורי (vector) מבוסס XML. לא ארחיב עליו כאן, אך אקצר ואסביר שבשונה מפורמטים כמו Bitmap או JPEG, קובץ SVG לא מכיל מידע אודות הפיקסלים שבתמונה, אלא מתאר את האלמנטים השונים המרכיבים את התמונה כגון קווים, צורות, טקסט וכד'. הנה דוגמה לקובץ SVG.

לשימוש ב-SVG ישנם מספר יתרונות על שימוש בפורמטים שאינם וקטורים:

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

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

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

למזלנו, קיים פתרון שימושי ביותר לבעייה זו:

פונט אייקונים

הרבה לפני שהומצא ה-SVG, כולנו הכרנו והשתמשנו בפורמט וקטורי אחר – פונט (גופן). בדומה ל-SVG, הייתרון המשמעותי של פונטים הוא שניתן לשנות את גודל האותיות ללא פגיעה באיכות. למעשה, אחד הסוגים של קובץ פונט מכיל קובץ SVG של ממש עבור כל אות.

קם חכם ואמר – מדוע צריכים הפונטים להכיל אותיות בלבד? למה שלא נייצר פונט המכיל, במקום אותיות, אייקונים? ואכן קיימים מספר פונטים שהם למעשה אוסף של אייקונים, ביניהם Font Awesome ו-Ionicons.

לפונט אייקונים יתרונות רבים:

  • בקשת HTTP אחת, ללא תלות במספר האייקונים אותם אנו צריכים.
  • תמיכה לאחור גם בדפדפנים ישנים מאוד (IE6).
  • ניתן לשנות את עיצוב האייקונים בנקל ע"י מאפייני CSS לעיצוב טקסט: font-size, color וכד'
  • השימוש בפונטים אלו הוא פשוט מאוד, ומצריך 2 שורות קוד בלבד:
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<i class="fa fa-camera-retro"></i>

השורה הראשונה מייבאת את קובץ ה-CSS של Font Awesome (שבתורו מייבא את הפונט עצמו), והשניה מייצרת תגית ריקה, שהחלק החשוב בה הוא המאפיין class, ה"ממלא" את התגית בתוכן המתאים מתוך הפונט (גם כאן לא נגלוש לפרטים).

וכך זה נראה:

JS Bin on jsbin.com

עבור אתרים ואפליקציות פשוטים – השימוש ב-Font Awesome ודומיו מספק בהחלט, בעיקר כי פונטים אלו מכילים אייקונים עבור רוב הפעולות הנפוצות. אך מה אם יש לנו אייקונים משלנו שנרצה לשלב בפונט? בדיוק בשביל משימה זאת קיים השירות המדהים הבא.

Fontello

פונטלו הוא שירות חינמי (קוד פתוח) המאפשר לנו לייצר פונט משלנו, המכיל בדיוק את האייקונים שאנחנו צריכים, ואותם בלבד. ניתן להוסיף אייקונים לפונט בשתי דרכים:

  • ע"י בחירה ממבחר קיים של מאות אייקונים מפונטים נפוצים כגון Font Awesome, Fontelico, Entypo ועוד.
  • ע"י העלאה של קבצי SVG משלנו.

לאחר בחירת האייקונים, ניתן להוריד את הפונט, שמגיע ב-4 פורמטים שונים ונתמך בכל הדפדפנים, ואיתו קבצי CSS המאפשרים שימוש באייקונים בקלות:

<link rel="stylesheet" href="path/to/fontello.css">
<i class="icon-like"></i>

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

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

6 טכניקות שיזניקו את קוד ה-web שלכם אל העתיד

אחת הבעיות המעיקות ביותר בפיתוח web, בין אם מדובר באתר פשוט או ב-web app מורכב, היא חוסר התמיכה של דפדפנים שונים בסטנדרטים חדשים של HTML, CSS ו-Javascript. בשנים האחרונות שפות אלו מתפתחות בקצב מסחרר, וליצרני הדפדפנים השונים קשה לעמוד בקצב. אנחנו כמפתחי web לרוב נרצה שהאתר שלנו יעבוד כמו שצריך על כל הדפדפנים הפופולאריים, אך לעיתים אפילו הגרסאות החדשות ביותר שלהם (שלא נדבר על גרסאות קודמות…) חסרות תכונות בסיסיות, מה שהופך תכונות אלו בפועל ללא שמישות.

לדוגמה, תחביר חדש ושימושי של javascript הוא arrow functions:

var func = (x, y) => x + y - 1;
func(3, 4); // 6

נכון לכתיבת שורות אלו השימוש ב-arrow functions לא אפשרי ב-Internet Explorer (באף גרסה), ואף יגרום ל-syntax error. הדבר מונע לחלוטין שימוש ביכולת זו באתר שאמור לתמוך ב-IE (אלא אם בוחרים להתעלם מבערך 10% מהגולשים שלנו).

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

#1 – הכרת השטח

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

Can I use

האתר המדהים הזה מחזיק רשימה מעודכנת של כל הפיצ'רים החדשים של HTML, CSS ו-Javascript ועבור כל אחד מהם מציג באיזה פלטפורמות הוא נתמך, ואיזה בעיות ידועות יש איתו.

למשל בתמונה למעלה ניתן לראות ש-CSS3 3D Transforms נתמכים בכל הדפדפנים למעט IE בגרסה 9 ומטה ו-Opera Mini. כעט ניתן להחליט האם להשתמש בפיצ'ר זה ולפגוע בנראות האתר בדפדפנים הישנים או לא.

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

ECMAScript Compatibility Table

בשונה משפות תכנות קונבנציונליות שיוצאות לשוק בגרסאות, Javascript, או בשמה החדש ECMAScript, היא אוסף גדול של יכולות בלתי תלויות. כל פלטפורמה (דפדפן, mobile os, מנוע JS) בוחר לממש חלק מהפיצ'רים האלו בסדר שרירותי כלשהו, כלומר תמיכה ביכולת אחת לא בהכרח מעידה על תמיכה באחרת.

ECMAScript Compatibility Table מרכז עבורנו בטבלאות ענקיות, אחת לכל גרסת JS, את המצב העדכני של כל אחד מהפיצ'רים בכל אחת מהפלטפורמות.

ECMAScript Compatibility Matrix
ECMAScript Compatibility Matrix

בדומה ל-Can I use, ניתן להשתמש במידע זה כדי להחליט באילו יכולות שווה לנו להשתמש, באילו לא, ולאילו אפשר לחפש חלופות.

#2 – Transpiling

בדומה לקומפילציה ההופכת קוד בשפה עילית לקוד בשפה נמוכה יותר (למשל קוד c לקוד בשפת מכונה), טרנספילציה היא המרה של קוד בשפה עילית אחת לקוד בשפה עילית אחרת, או אפילו אותה שפה אך שימוש בתחביר פשוט יותר.

Javascript Transpilers

קיימים מספר טרנספיילרים עבור javascript, והמועדף עליי הוא Babel (בעבר 6to5). כלי זה ממיר שימוש בפיצ'רים חדשים של JS לקוד המבצע את אותה פעולה בדיוק (או בערך), אך ע"י שימוש בקוד JS הנתמך בכל הדפדפנים והפלטפורמות.

כמו שניתן לראות כאן, נכון לכתיבת שורות אלו, מבין כל פלטפורמות ה-JS השונות Babel תומך כמעט במספר הרב ביותר של פיצ'רים (שני רק ל… Edge?!), מה שמבטיח לנו שכמעט כל יכולת חדשה של JS שנשתמש בה תעבוד על כל הדפדפנים.

CSS Transpilers

גם עבור CSS ישנה סוג של טרנספילציה המיועדת לתמיכה בדפדפנים ישנים, זאת עקב העובדה שישנם מאפייני CSS הנתמכים בדפדפן מסויים רק אם מוסיפים תחילית ספציפית (vendor prefix) לאותו דפדפן. לדוגמה, נכון לכתיבת שורות אלו, המאפיין appearance יעבוד על רוב הדפדפנים רק אם נשתמש בו כך:

div {
  appearance:button;
  -moz-appearance:button; /* Firefox */
  -webkit-appearance:button; /* Safari and Chrome */
}

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

תהליך בנייה

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

#3 – Polyfills

יכולות מסויימות של Javascript או HTML הן מורכבות מדי כדי לממש בתוך טרנספיילר, למשל SVG, Canvas, HTML Storage ועוד. כדי שבכל זאת נוכל להשתמש בהן, נכתבו עבורן תחליפים עבור דפדפנים ישנים. תחליפים אלו נקראים polyfills (ולעיתים shims). קיימים Polyfills כמעט עבור כל תכונה חדשה של HTML או JS, ורשימה די מקיפה שלהם ניתן למצוא כאן.

ברוב המקרים polyfill לא ירוץ אם הוא יזהה שהיכולת שהוא אמור להחליף כבר קיימת בדפדפן הנוכחי, ולכן זה בטוח להכיל את ה-polyfills בקוד שלנו תמיד. אך שימו לב ש-polyfills הם בסופו של דבר קוד JS, ושימוש מופרז בהם יכול להגדיל את הקוד שלכם משמעותית. אם לא אכפת לכם לבצע בקשת HTTP נוספת, תוכלו להשתמש בשירות polyfills.io, המוריד את ה-polyfills הנדרשים לפי ה-user agent של הדפדפן, וכך יכול לחסוך רוחב פס יקר.

#4 – זיהוי בזמן אמת ומימוש חלופות

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

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

לאחר ריצה של כל בדיקה תתווסף מחלקה (class) לתגית ה-html של האתר שלכם, שתאפשר לכם לבנות התנהגות שונה, אפילו בקוד ה-CSS עצמו, במידה ויכולת מסויימת ממומשת או לא. כמובן שניתן לגשת לתוצאות הבדיקות גם באמצעות JS.

#5 – בדיקות, בדיקות, בדיקות

אז סיימתם לבנות את האתר שלכם, ווידאתם שהוא עובד מעולה ב-Chrome ,Firefox ו-IE בגרסאות המותקנות לכם על המחשב. אבל מה עם גרסאות דפדפן ישנות? ומובייל? ודפדפנים איזוטריים?

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

  • אתרים כמו Browsershots (ויש עוד עשרות כאלו) יצלמו עבורכם את האתר שלכם בכל דפדפן שתבחרו. כך תוכלו לזהות במהירות היכן יש בעיות נראות לעין ולהתמקד בדפדפנים הבעייתיים.
  • השירות BrowserStack מאפשר לכם להתחבר לעשרות גרסאות של דפדפנים על כל סוגי מערכות ההפעלה (כן, גם מובייל) ולבדוק בפועל את האתר שלכם עליהם. ניתן אפילו לבדוק את האתר כאשר הוא רץ על המחשב שלכם!!
  • אם אין לכם כח לבדוק ידנית כל פעם, ואין לכם צוות QA פנימי, חברת Rainforest QA מספקת שירותי QA as a Service, ומאפשרת לכם להגדיר בדיקות ידניות, שיבוצעו ע"י בודקים אנושיים באופן מיידי. אגב, הם משתמשים ב-Mechanical Turk.
  • ואם יש לכם קצת זמן פנוי, אולי שווה לכם לכתוב בדיקות אוטומטיות באמצעות Selenium. ישנם שירותים (BrowserStack ביניהם) המאפשרים לכם להריץ את בדיקות ה-Selenium שלכם בענן על עשרות דפדפנים שונים.

#6 ואחרון – ניטור שגיאות

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

אך זה שמשהו קורה אי שם אצל הלקוח לא אומר שאי אפשר לדעת עליו ולטפל בו!

באמצעות הוספת קוד פשוט שידווח לכם בכל מופע של האירוע window.onerror, ניתן לקבל התראות בזמן אמת על שגיאות javascript שמופיעות אצל הלקוח. אמנם קצת קשה לזהות כך בעיות נראות של האתר, אבל היי – גם זה משהו!

קטע הקוד הזה מדגים ניטור שגיאות מהסוג הזה, וגם יודע להתעלם מ-false positives מוכרים שנגרמים לרוב ע"י פלאגינים של הדפדפן ושירותי צד שלישי למיניהם.

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

לסיכום

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