Недавно со мной случилась одна весёлая история. Я сделал веб-проект и расширил возможности уже существующего приложения, которым в моей организации пользуются кадровики. Всё выглядело просто отлично, я радовался тому, что проект был запущен, с нетерпением ожидая благодарственных писем.

Через несколько дней после первого релиза я, и правда, начал получать письма. Но благодарностей в них не наблюдалось. Мне писали менеджеры, работники кадровой службы, и все те, кто пытался воспользоваться моей программой. Все они говорили, что у них приложение работает неправильно.



В чём же дело? А дело в том, что я, создавая проект, тестировал его в Chrome. Но пользователи этого проекта постоянно применяют Firefox и IE. Работа с моим приложением не стала исключением. Мне, в итоге, было совсем невесело от того, что проект, запущенный пару дней назад, надо было дорабатывать.

Собственно говоря, тут мне на помощь и пришли полифиллы.

Полифиллы


Полифилл (polyfill или polyfiller) — это фрагмент кода (или некий плагин), реализующий то, наличия чего разработчик ожидает среди стандартных возможностей браузера. Полифиллы позволяют, так сказать, «сгладить» неровности браузерных API.

В веб-среде полифиллы обычно представлены JavaScript-кодом. Этот код используется для оснащения устаревших браузеров современными возможностями, которые эти браузеры не поддерживают.

Например, с помощью полифилла можно сымитировать функционал HTML-элемента Canvas в Microsoft Internet Explorer 7. Для этого применяется плагин Silverlight. Средствами полифилла может быть реализована поддержка единиц измерения rem в CSS, или атрибута text-shadow, или чего угодно другого. Причины, по которым разработчики не пользуются исключительно полифиллами, не обращая внимание на встроенные возможности браузеров, заключаются в том, что стандартные возможности браузеров обеспечивают более качественный функционал и более высокую производительность. Собственные браузерные реализации различных API обладают более широкими возможностями, чем полифиллы, да и работают быстрее.

Иногда полифиллы используются для решения проблем, связанных с тем, что различные браузеры по-разному реализуют одни и те же возможности. Подобные полифиллы взаимодействуют с некоторыми браузерами, используя их нестандартные особенности, и дают другим JavaScript-программам доступ к определённым механизмам, соответствующий стандартам. Надо отметить, что подобные причины использования полифиллов сегодня уже не так актуальны, как раньше. Особую распространённость полифиллы имели во времена IE6, Netscape и NNav, когда каждый браузер реализовывал возможности JavaScript не так, как другие.

Пример


Недавно я опубликовал руководство по разработке приложения, конвертирующего CSV и Excel-файлы в JSON с помощью JavaScript. Здесь можно посмотреть на готовое приложение.

Для того чтобы разобраться с тем, о чём мы будем говорить дальше, вы можете либо сделать всё то, о чём идёт речь в руководстве, либо клонировать мой репозиторий следующей командой:

git clone https://github.com/YannMjl/jsdemo-read-cvs-xls-json
cd jsdemo-read-cvs-xls-json

Рекомендую в процессе работы пользоваться VS Code. Запустить веб-приложение можно локально, с использованием расширения для VS Code Live Server.

Давайте модифицируем это веб-приложение и посмотрим на проблемы, которые возникают при работе с ним с использованием разных браузеров.

Создадим в репозитории ветку polyfill и переключимся на неё:

git checkout -b polyfill

Я собираюсь исследовать ситуацию, в которой мы получаем данные из двух или большего количества CSV-файлов, и, после завершения обработки результатов запросов к соответствующим API, выводим эти данные в HTML-таблицу.

?Доработка проекта


Создадим в корневой директории проекта новый CSV-файл (team2.csv), в результате чего там должно оказаться два файла. Вот файл, который я добавил в проект.

Модифицируем файл script.js так, чтобы он читал бы данные из 2 файлов и выводил бы все данные в HTML-таблицу. Вот мой script.js:

// ********************************************************************
// Глобальные переменные                                              *
// объявим глобальные переменные, которые будут использоваться в коде *
// ********************************************************************
var csv_file_API_1 = "./UsersSample.csv";
var csv_file_API_2 = "./team2.csv";
var APIs_array = [csv_file_API_1, csv_file_API_2];

// Выполняем некие действия при загрузке страницы
$(document).ready(function () {

    $("#headerTitle").hide(300).show(1500);

    makeAPICalls();

}); // end: document.ready()

function makeAPICalls() {

    // Массив, который будет содержать обращения к API
    var calls = [];

    // Передача API для CSV-файлов коллбэкам
    APIs_array.forEach(function (csv_file_API) {

        // Помещение промиса в массив вызовов
        calls.push(new Promise(function (resolve, reject) {

            // Выполнение вызова API с использованием AJAX
            $.ajax({

                type: "GET",

                url: csv_file_API,

                dataType: "text",

                cache: false,

                error: function (e) {
                    alert("An error occurred while processing API calls");
                    console.log("API call Failed: ", e);
                    reject(e);
                },

                success: function (data) {

                    var jsonData = $.csv.toObjects(data);

                    console.log(jsonData);

                    resolve(jsonData);
                } // end: обработка данных при успешном обращении к API

            }); // end: AJAX-вызов

        })); // end: добавление данных при вызове промисов

    }); // end: обход массива API


    // После завершения всех обращений к API
    Promise.all(calls).then(function (data) {

        // объединим все данные
        var flatData = data.map(function (item) {
            return item;
        }).flat();

        console.log(flatData);

        dislayData(flatData);
    });

}

function dislayData(data) {
    
    $.each(data, function (index, value) {

        $('#showCSV').append(

            '<li class="list-group-item d-flex justify-content-between align-items-center">' +

                '<span style="width: 15%; font-size: 1rem; font-weight: bold; color: #37474F">' +
                    value['FIRST NAME'] +
                '</span>' +

                '<span style="width: 15%; font-size: 1rem;  color: #37474F">' +
                    value['LAST NAME'] +
                '</span>' +

                '<span class="badge warning-color-dark badge-pill">' +
                    value['PHONE NUMBER'] +
                '</span>' +

                '<span class="badge success-color-dark badge-pill">' +
                    value['EMAIL ADDRESS'] +
                '</span>' +

                '<span class="badge badge-primary badge-pill">' +
                    value.CITY +
                '</span>' +

                '<span class="badge badge-primary badge-pill">' +
                    value.STATE +
                '</span>' +

            '</li>'
        );

    });
}

Теперь, скопировав адрес страницы, откройте проект во всех браузерах, которые у вас есть. В моём случае это были Internet Explorer, Firefox Mozilla, Microsoft Edge и Google Chrome. Оказалось, что приложение перестало нормально работать в Internet Explorer и Microsoft Edge. Там выводились только заголовки.


Страница проекта в Chrome


Страница проекта в Microsoft Edge

У того, что на странице, выводимой некоторыми браузерами, нет данных, две причины:

  1. Я пользовался промисами и коллбэками, которые поддерживают не все браузеры. Например, среди таких браузеров — IE и Edge.
  2. Я пользовался методом массивов flat() для того, чтобы создать из существующего массива новый «плоский» массив. Этот метод не поддерживается некоторыми браузерами. Среди них, как и в предыдущем случае, IE и Edge.

?Применение полифиллов


Исправим проблему промисов и коллбэков, воспользовавшись библиотекой Bluebird. Это — полномасштабная JS-реализация механизмов, связанных с промисами. Самая интересная особенность библиотеки Bluebird заключается в том, что она позволяет «промисифицировать» другие Node-модули, обрабатывая их так, чтобы с ними можно было бы работать асинхронно. Такую обработку можно применить к коду, в котором используются коллбэки.

Загрузим библиотеку Bluebird на страницу, воспользовавшись соответствующим CDN-ресурсом. Для этого разместим в заголовке файла index.html (в элементе head) следующее:

<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.7.0/bluebird.min.js"></script>

Для того чтобы исправить проблему, касающуюся метода массивов flat(), добавим в верхнюю часть файла script.js следующий код:

Object.defineProperty(Array.prototype, 'flat',
    {
        value: function (depth) {
            depth = 1;
            return this.reduce(
                function (flat, toFlatten) {
                    return flat.concat((Array.isArray(toFlatten) && (depth > 1)) ? toFlatten.flat(depth - 1) : toFlatten);
                }, []
            );
        },
        configurable: true
});

Теперь приложение должно работать во всех браузерах так, как ожидается. Вот, например, как оно теперь выглядит в Microsoft Edge.


Страница доработанного проекта в Microsoft Edge

Я развернул этот проект здесь. Можете его испытать.

Если у вас не получилось добиться работоспособности проекта — загляните в мой репозиторий.

А вот — для примера — ещё пара полифиллов.

// полифилл для String.prototype.startsWith()
if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        value: function (search, rawPos) {
            pos = rawPos > 0 ? rawPos | 0 : 0;
            return this.substring(pos, pos + search.length) === search;
        }
    });
}

// полифилл для String.prototype.includes()
if (!String.prototype.includes) {
    String.prototype.includes = function (search, start) {
        'use strict';
        if (typeof start !== 'number') {
            start = 0;
        }

        if (start + search.length > this.length) {
            return false;
        } else {
            return this.indexOf(search, start) !== -1;
        }
    };
}

Итоги


Полифиллы были особенно актуальны раньше, но и в наши дни они способны помочь в разработке кросс-браузерных веб-проектов. Надеемся, что приведённый здесь пример позволил тем, кто раньше не знал о полифиллах, по-новому взглянуть на проблему создания сайтов, рассчитанных на различные браузеры.

Уважаемые читатели! Пользуетесь ли вы полифиллами?


Комментарии (22)


  1. vanxant
    13.11.2019 14:20

    Буду краток (с).
    На дворе 2020, не используйте полифилы.
    Если что-то работает в хроме, но не работает в ФФ хотя бы в найтли (или наоборот) — значит фича пока сырая, глюкавая и будет дорабатываться.
    Если юзерам нужен ИЕ, то скорее всего у них ХР. Подумайте, нужны ли в 2020 вам такие юзеры. Если даже нужны — пересаживайте их на ESR-версии хрома/ФФ/оперы, там хотя бы асинки работают и большинство из нужных фич.
    Полифилы мало того что раздувают ваш бундль, они ещё часто тормозят всех. JS полифил всегда будет выполняться медленнее, чем нативная реализация того же в браузере.
    На крайний случай делайте две сборки — нормальную и легаси с полифилами. Vue например умеет такое «из коробки», но не всем так повезло.


    1. VolCh
      13.11.2019 14:36

      Подумайте, нужны ли в 2020 вам такие юзеры.

      Вот у нас бизнес подумал и сказал, что нужны. Заметную часть доходов приносят пользователи IE в b2b сфере у многих, насколько я знаю.


      1. vanxant
        13.11.2019 14:45

        А у вас бизнес какие-либо бизнес-метрики снимал с этих юзеров? РОИ там, нагрузка на техподдержку, затраты на тестирование для старых ИЕ, вот это вот всё? Проблема ведь не только в том, что там браузер старый, там скорее всего и комп очень старый (медленный проц, мало ОЗУ). Оно и так еле шевелится, а если туда ещё и полифилов напихать — то по факту приложение запускается, но сколько-нибудь нормально не работает.


        1. VolCh
          13.11.2019 19:23

          Какие-то снимали. И как-то работает, потому что малейший косяк с полифилами в релизе и баги очень быстро приходят уже до разработчиков


    1. HawkeyePierce89
      13.11.2019 15:03

      А как быть с пользователями, у которых старый Хром 49, потому что они сидят на каком-нибудь Android 5 или 6 и выходят в интернет со стандартного встроенного браузера «Браузер» или «Интернет»? И их, к сожалению, далеко не 1% пользователей.


      1. vanxant
        13.11.2019 15:17

        Ну хром 49 очень далеко ушёл по сравнению с ИЕ9 (ХР), по крайней мере промисы и let/const понимает.


    1. kurtov
      13.11.2019 19:17
      +2

      раздувают ваш бундль, они ещё часто тормозят всех

      Мы используем такой подход: современные браузеры не тормозим, старые и так медленные.
      Первым идёт небольшой скрипт который проверяет фичу и синхронно подключает скрипт полифила.
      !window.Promise && document.write('<script src="путь"></script>')

      В итоге новый браузер пробегает десяток if и быстро работает, старые браузеры ждут пачки полифилов.

      Правда это не работает с async\await и другим новым синтаксисом, но тут мы планируем собирать два бандла.


    1. xitt
      13.11.2019 20:21
      +3

      Если юзерам нужен ИЕ, то скорее всего у них ХР. Подумайте, нужны ли в 2020 вам такие юзеры.


      Ушел пересаживать тысячу-другую своих бухгалтеров и юристов на Линукс, пришла команда из хабра.


      1. vanxant
        13.11.2019 21:06
        +1

        Рекомендую сначала забрать счёты:)


    1. Vitaly83vvp
      14.11.2019 10:26
      +1

      Пользователи с IE в 2020 году очень даже часто встречаются. И не только c XP. Уже давно с семёркой, как минимум.
      А сидят они в IE не по своей прихоти или нежелании переходить на другие браузеры. Основная часть этих пользователей работает или в банковской сфере или работают с банками (или с госструктурами). Он поддерживает нужный тип шифрования. Для этого же браузера пишут специальные расширения для работы. Кроме того, в таких организациях очень не любят что-то менять, если и так хорошо работает.
      В части, что полифилы медленнее нативной реализации, согласен, но, когда нет выбора, то без них не обойтись.


      1. justboris
        14.11.2019 11:06

        Кроме того, в таких организациях очень не любят что-то менять, если и так хорошо работает

        В том-то и дело, что работает плохо. Explorer устаревший и самый небезопасный браузер и ничего хорошего там нет. Сидят на нем не от хорошей жизни, и я не понимаю, зачем оправдывать этот старый хлам.


        1. Vitaly83vvp
          14.11.2019 11:15

          Я не оправдываю. На счёт «работает хорошо», я имел в виду про налаженный процесс, а не про работу самого браузера.
          По правде говоря, я и сам не понимаю этого требования на счёт IE. Нормальная система должна работать во всех браузерах. Вот, поэтому, там, где невозможно использовать браузеры актуальной версии или поддерживающие стандартные функции и приходится использовать полифилы (как по мне, частный случай «костылей»).


      1. vanxant
        14.11.2019 16:15

        Это какие такие банки в 2020 требуют ИЕ? Мне кажется лет 5 как вымерли.


      1. vanxant
        14.11.2019 16:20

        в таких организациях очень не любят что-то менять

        Ну вот, вот настоящая причина. Вон выше xitt 20 лет назад освоил администрирование ХР, и теперь все его «тысячи-другие бухгалтеров и юристов» должны страдать.
        Ну это его проблемы, а речь про разработчиков. Под ИЕ мало просто поставить полифилы, там слишком много всего надо тащить. Вы не можете использовать допустим нативные мультимедиа АПИ напрямую, вы должны брать либу, которая их оборачивает (и тормозит на нормальных браузерах) и тянет флэш для ИЕ. И так куда ни плюнь. А потом всё это нужно тестировать, в том числе в ИЕ. Со своими тысячами бухгалтеров такие как товарищ выше пусть разбираются сами, но почему миллионы разработчиков должны страдать?


        1. VolCh
          16.11.2019 08:56

          Разработчик волен выбирать поддерживать ему ИЕ и ко или нет. Есть проекты, где даже в вакансиях выдают за свой плюс: "ИЕ не поддерживаем", а крепостное право отменили. Более того, можно даже свой бизнес открыть, если никак не можешь найти проект, где бизнес считает выгодным не поддерживать ИЕ.


          А так большинство разработчиков поддерживают ИЕ, потому что это входит в условия их работы, они так договорились с работодателем. Уж что-что, а не разу не слышал даже, чтобы поддержка ИЕ появилась в проекте, чтоб пришёл начальник и сказал "ребята, с сегодняшнего дня мы поддерживаем ИЕ" (вернее в третьем тысячелетии ни разу не слышал :) )


  1. noomorph
    13.11.2019 16:38

    Уважаемые читатели! Пользуетесь ли вы полифиллами?

    Да, пользуюсь. Давеча было два юзкейса:


    1. <dialog> polyfill (caniuse)
    2. :focus-visible polyfill (caniuse)

    Первый уже внедрил на одном проекте, а над вторым как раз подумываю. Какие у вас впечатления по этим полифиллам, если пользовались?


    Мне обе фичи очень нравятся: диалоги — своей семантичностью и поддержкой нескольких плюшек (form method=dialog, showModal(), ::backdrop), а :focus-visible — возможностью сделать ненавязчивый focus ring, включающийся только при работе с клавиатуры.


  1. AriesUa
    13.11.2019 18:16
    +1

    Что касается полифилов. То лучше использовать webpack, который будет компилировать ваш ESNext to ES5. А уже скомпилированный бандл подключать в приложение.

    По крайней мере проблему flat и Promise вы бы точно решили.


    1. VolCh
      13.11.2019 19:25

      Просто компиляция в ES5, увы ещё не обеспечивает полифиллы


  1. Taraflex
    14.11.2019 01:26

    ИМХО, способ описанный в статье — путь в никуда.
    Наиболее адекватное найденное решение на 2019 год — www.npmjs.com/package/webpack-polyfill-injector
    Оно конечно не идеально, но на порядок лучше чем искать и подключать необходимые полифиллы ручками из разных источников.


  1. boojum
    14.11.2019 09:52

    Тестировать достаточно всегда только в IE — если в нем нормально заработает — будет работать везде )


    1. VolCh
      14.11.2019 10:51
      +1

      Неправда. У IE есть то, что не заработает в других браузерах.


      1. boojum
        14.11.2019 11:15

        Конечно же речь не про специально написанное для IE.