Иконки в проекте часто становятся причиной проблем разбухания размера бандла. Все из-за того что svg-иконки могут быть достаточно объемными.

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

На скриншоте видно, что это приложение, состоящее из 2х элементов, весит 722kb. Но почему?

Исходные данные

Давайте посмотрим на код, в котором и находится проблема.

Вроде все просто. У иконки по пропсу name мы понимаем, какую именно иконку из пака нам нужно достать. Но бандл слишком много весит, тут же только одна иконка, почему так? Давайте посмотрим, что происходит внутри компонента иконки.

./components/Icon
./components/Icon

Тут видно, что виной раздувания бандла является fontawesome и его наборы пакетов: free-regular-svg-icons, free-brands-svg-icons, free-solid-svg-icons, и т.д.

Мы импортируем сразу все иконки из всех наших пакетов, что и является основной проблемой.

Теперь взглянем на результаты анализа бандла.

602 kb сжатых gzip данных мы заставляем грузить наших пользователей только для того, чтобы показать им одну иконку! С этим точно нужно что-то сделать.

Сразу оговорюсь, вы можете сказать, что можно было бы загружать только нужные нам иконки через прямые импорты, а потом прогнать из через svg-sprite. Но тогда это привело бы к значительной переработке имеющихся проектов, что было бы крайне нежелательно.

Мне хотелось бы сохранить интерфейс взаимодействия с компонентом Icon и не заставлять разработчиков каждый раз добавлять новые иконки в проект и писать какой либо дополнительный код. То есть все должно остаться как есть, и при этом наша проблема должна быть решена.

Решение

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

Благодаря этим файлам наш бандлер поймет что нужно будет создать отдельный чанк под каждую иконку. Тогда мы сможем скачивать их по требованию.

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

Поэтому был написан скрипт который сделает всю рутинную работы за нас.

Результат работы скрипта iconPackMapsGenerator
Результат работы скрипта iconPackMapsGenerator

Внутри конфиг для его работы.

  • name: название генерируемого файл;

  • lib: путь до папки с файлами иконок;

  • prefix: префикс для названия иконки. Заменяет префикс fa.

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

И переделываем наш компонент Icon не трогая его интерфейс.

components/Icon.tsx
components/Icon.tsx

Вроде все. Что же у нас получилось в итоге?

Давайте вновь посмотрим на бандл.

Как видно, каждая наша иконка помещена в отдельный чанк, и также можно заметить, что размер бандла значительно увеличился. Было 600kb стало 2.01 Mb. Почему так?

Бандлер для создания чанка оборачивает его в специальный код который, как оказалось, весит больше чем многие наши svg.

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

А что же происходит теперь в браузере?

Теперь при загрузке страницы мы видим, что последний чанк размером 1.1kb это наша иконка.

При первой загрузки в начальном варианте мы загружали 722kb, а сейчас 89kb! Уже победа. Помимо этого мы получили:

  1. Сохранение обратной совместимости с исходным проектом;

  2. Динамическая загрузка иконок по требованию;

  3. Возможность добавлять кастомные иконки

Заключение

Надеюсь эта статья вам поможет с оптимизацией ваших бандлов и сэкономит вам время и силы. Исходных код используемого тестового проекта вы можете найти по ссылке.

Также если вам интересно в своем Telegram я время от времени выкладываю интересные находки по фронтенду. И всем легких бандлов.

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


  1. Carduelis
    20.06.2022 15:23

    Хорошая статья для понимания того как работает процесс бандлинга. Однако, оверхед в 1,5мб довольно высок, и многие фреймворки имеют автоматическое создание чанков по страницам, компонентам, где оверхед чанка уже незаметен.


    1. dpereverza Автор
      20.06.2022 15:27

      В статье я небольшой пример рассматриваю, а в рабочем проекте мы используем платные иконки, а их очень много. Только на скачивание иконок уходило 1.5 mb. Так что это был единственный выход. Или нужно было рефакторить все иконки на проекте


      1. Carduelis
        20.06.2022 22:58

        Возражу, в таком смысле можно было бы сконфигурировать webpack на генерацию статических svg, со свойствами fill="currentColor" и т.д., и не иметь оверхеда.

        Автоматический бандлинг имеет смысл, так как на среднем проекте очень часто используется максимум 5-10-20 общих иконок (стрелочки, человечки, т.д.), если у вас на проекте сотни разных иконок, то скорее всего, довольно весомая часть из них встречается в одном React-компоненте, который и стоит отправлять в отдельный чанк, возможно, с целом куском UI-я, который окружает эту иконку.

        Конечно, при бесконечном большом проекте, конечно, бесконечное число иконок будет иметь смысл превратить в бесконечное число мини-чанков, однако в таком крайнем случае просто svg-иконки как svg-файлы будут самым верным решением.


        1. dpereverza Автор
          21.06.2022 09:17

          В таком случае нужно будет:

          1. переделать получение иконок через прямые импорты (пока обратная совместимость);

          2. выделять время на разбиение частей проекта на чанки;

          3. описать состояния для этапов их загрузки.

          Тоже вариант, но труднозатратнее.


          1. Fragster
            21.06.2022 11:33
            +1

            Думаю, при грамотном подходе получится один раз пройтись глобальным поиском и заменой с регэкспом по всему проекту и всё. Трудозатраты примерно в пределах часа. А выхлоп прям большой. Лично я перешел на прямой импорт svg и https://www.npmjs.com/package/svg-sprite-loader - и очень доволен. При этом можно загружать иконки из разных библиотек и из своих ассетов вперемешку - что прям полезно бывает.