Babel Polyfills

מתי, איך ולמה משתמשים בפוליפילים כדי לאפשר אובייקטים ותכונות מתקדמות גם לסביבות מיושנות
Babel Logo

לא כל הפיצ'רים של ES6 נולדו שווים, חלק מהפיצ'רים הם בבחינת סוכר סינטקסי – כלומר השפה השתנה על מנת שיהיה יותר קל לקרוא ולכתוב את הקוד. חלק מהפיצ'רים האחרים ניתנים לשכתוב על ידי ES5. אפילו גנרטורים. אם תציצו באתר ES6 features בחלק של גנרטורים, תוכלו לראות שניתן להמיר את הקוד של גנרטורים לקוד של ES5.
המצב מסתבך עם פיצ'רים כמו promise או weakMaps שלא ניתן לשכפלם בשום פנים ואופן ב-ES5. למה? כשאנו כותבים Promise למשל, אנחנו מניחים שיש אובייקט גלובלי שקוראים לו Promise. גם WeakMap עובדת כך. אם אנו מניחים מתודות סטטיטיות באובייקטים – כמו Object.assign.
במקרה הזה, אם אני אנסה לקמפל את הקוד של Promise ב-Babel, אני אראה שהוא מתקמפל אבל ה-Promise נשאר, מה שיגרום לשגיאה בוודאות בדפדפנים או ב-node מגרסה v1.2.0 ומטה.

בוא ונסתכל על הקוד החביב הזה שכתוב ב-ES6 – מדובר בקוד שאנחנו כבר מכירים


let myPromise = new Promise(function(resolve, reject) {
  let request = new XMLHttpRequest();

  request.open('GET', 'http://api.icndb.com/jokes/random');
  request.onload = function() {
    if (request.status == 200) {
      resolve(request.response); // we got data here, so resolve the Promise
    } else {
      reject(Error(request.statusText)); // status is not 200 OK, so reject
    }
  };

  request.onerror = function() {
    reject(Error('Error fetching data.')); // error occurred, reject the  Promise
  };

  request.send(); //send the request
});

myPromise.then(function(data) {
  console.log('Got data! Promise fulfilled.');
  document.getElementById('container').textContent = JSON.parse(data).value.joke;
}, function(error) {
  console.log('Promise rejected.');
  document.getElementById('container').textContent = 'Sorry, something wrong. please refresh';
  console.log(error.message);
}) 

יש בו let ויש בו Promise, אם נקמפל אותו עם Babel ועם ES2015 preset שאמור לקחת את כל הפיצ'רים של ES6 ולהמיר אותם ל-ES5 נראה שהקוד המקומפל נראה כך (הדגשתי את מה שחשוב) :


'use strict';

var myPromise = new Promise(function (resolve, reject) {
  var request = new XMLHttpRequest();

  request.open('GET', 'http://api.icndb.com/jokes/random');
  request.onload = function () {
    if (request.status == 200) {
      resolve(request.response);
    } else {
      reject(Error(request.statusText));
    }
  };

  request.onerror = function () {
    reject(Error('Error fetching data.'));
  };

  request.send();
});

myPromise.then(function (data) {
  console.log('Got data! Promise fulfilled.');
  document.getElementById('container').textContent = JSON.parse(data).value.joke;
}, function (error) {
  console.log('Promise rejected.');
  document.getElementById('container').textContent = 'Sorry, something wrong. please refresh';
  console.log(error.message);
});

הקימפול העיף את ההערות ושינה את let ל-var אבל לא נגע ב-Promise! למה?

התשובה היא שכאן מדובר באובייקט גלובלי ודרך הטיפול של Babel בפיצ'רים גלובליים היא מעט שונה ותלויה בר אם אני עובד עם Babel בדפדפן או ב-node (וגם שם יש כמה דרכים לטפל בזה). הדרך שאותה אסקור כאן היא Polyfills. פוליפילים זה קטעי ג'אווהסקריפט שמכניסים פונקציונליות חדשה לגמרי לדפדפנים או לסביבות הרצה. למשל, ב-CSS היינו משתמשים בעבר בפוליפילים שידמו פינות עגולות או פיצ'רים של HTML5 באקספלורר מצחין דפדפנים מיושנים אלו ואחרים.

פוליפיל הוא פשוט קטע קוד בג'אווהסקריפט שמדמה פונקציה אינהרנטית בדפדפנים מיושנים. הפוליפיל של Promise בודק אם יש את האובייקט הגלובלי Promise במידה וכן, סימן שהדפדפן/סביבת ההרצה תומכת ב-Promise. במידה ולא, הוא יוצר את האובייקט הגלובלי Promise ומכניס לתוכו את כל הפונקציונליות שאמורה להיות שם – למשל את המתודות resolve, then ו-reject והכל ב-ES5 תקני למהדרין. כך שגם דפדפנים/סביבות שלא מכירים כלל ES6, ידעו מה לעשות איתו. זה רץ פחות טוב מן הסתם מ-Promise טבעי, אבל זה רץ.

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

npm install --save-dev babel-polyfill

ועושים include באופן הבא:




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


require("babel-polyfill");

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

נשמע פשוט, נכון? אבל צריך לזכור כמה דברים:

הפוליפיל חייב לרוץ קודם

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

var promise = new Promise(function(resolve, reject) {
                   ^
ReferenceError: Promise is not defined

סימן שיש קריאה ל-Promise לפני שהוא רץ.

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

פוליפיל מכניס אובייקט/מתודות גלובליים

הפוליפיל מכניס promise גלובלי (או אובייקטים גלובליים אחרים). זה עלול לגרום לבעיה משמעותית אם יש מודול אחר או קטע קוד אחר שמטפל בו. אם אתם כותבים פלגין/מודול, אל תניחו שיש אובייקט Promise גלובלי אלא השתמשו ב-babel-plugin-transform-runtime – שזה תוסף של Babel. במאמר הבא אסביר באופן מפורט על תוספים

נושא הפוליפילים עלול לבלבל, מי שמתבלבל – פשוט צריך לזכור להכניס את הפוליפיל של Babel אם הוא משתמש ב-promises או באובייקטים גלובליים אחרים ולהשתמש בכל הפיצ'רים שהוא רוצה בלי לחשוש. מי שלא מפחד להיכנס קצת לעומק – צריך לבדוק במה הוא משתמש ולבצע את השמת הפוליפילים באמצעות core-js או בדרכים אחרות. ברוב המקרים, על מנת לתמוך בסביבות ישנות יותר, אנחנו נכניס את Babel polyfill לאפליקציה שלנו ונשכח ממנה.

אתם בטח שואלים את עצמכם למה זה לא נעשה אוטומטית. התשובה היא שלא כולם רוצים להשתמש בפוליפילים משלוש הסיבות שהזכרתי קודם לכן. גרסה 6 של Babel מאפשרת מודולריות ולכל אחד להכניס את מה שהוא רוצה להכניס בלי לדחוף את הכל לגרון. אם אנחנו רוצים פוליפיל ויש לנו promise או כל אובייקט גלובלי אחר, אנחנו נשתמש בו. אם לא, אז לא. אל תשכחו ש-Babel אמורה בעתיד לעבוד גם עם ES2017 וצפונה ולבצע התאמות ל-ES6 מ-ES2018 (למשל) אז הגישה המודולרית הזו היא קריטית להמשך.

פוסטים נוספים שכדאי לקרוא

רספברי פיי

מה זה AIoT? ואיך אפשר להתחיל?

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

גלילה לראש העמוד