Часто ли вам доводилось импортировать в проект целый JavaScript-пакет, когда нужно было воспользоваться лишь очень немногими возможностями этого пакета? Вероятно, вы сможете вспомнить много таких случаев.
Ярким примером пакета, который импортируют целиком, нуждаясь лишь в нескольких его функциях, можно назвать библиотеку Lodash. Если вы не слышали об этой библиотеке — вам стоит взглянуть на её документацию. В частности, на сайте проекта можно узнать о том, что Lodash упрощает JavaScript-разработку, беря на себя решение задач по работе с массивами, числами, объектами, и прочим подобным.
Библиотека Lodash включает в себя более 200 функций. Это говорит о том, что она, и правда, способна помочь программисту в решении массы задач. Но может случиться так, что, импортировав всю библиотеку, вызовут лишь 4-5 функций. Это приводит нас к вопросу о целесообразности импорта всего пакета в ситуации, когда использовано будет лишь 2-3% его возможностей. Подумаем о том, как с этим справиться.
Функция get из библиотеки Lodash
Одна из моих любимых возможностей библиотеки Lodash представлена функцией get. Она позволяет организовать безопасный доступ к вложенным объектам и поддерживает применение значений, задаваемых по умолчанию.
Вот пример использования этой функции:
const _=require('lodash'); let employee1={ name : "Jon", address : { street : "North Avenue", area : "DAC", zip : "87344", contact : [ 12444554, 9384847 ] }, designation : "Manager" }; let employee2={ name : "Jake", designation : "Senior Manager" }; function getHomeContact(employee) { return employee.address.contact; } getHomeContact(employee1); // [12444554, 9384847] getHomeContact(employee2); // Uncaught TypeError: Cannot read property 'contact' of undefined function getHomeContactWithLodash(employee) { return _.get(employee, "address.contact", []); } getHomeContactWithLodash(employee1); // [12444554, 9384847] getHomeContactWithLodash(employee2); // []
Использование этой функции позволяет сделать код гораздо чище, чем прежде. Это помогает избежать ошибок из-за того, что, когда ожидается пустой массив, функция не вернёт null. Благодаря этой функции нельзя, по ошибке, вызвать метод map у пустого массива. Она защищает и от других неприятностей.
Взглянем на то, как повлияет на размер бандла включение в проект библиотеки Lodash в том случае, если планируется использовать лишь функцию get. Эксперименты будут проводиться с использованием React-проекта. Размер бандла будет проанализирован до импорта библиотеки и после различных вариантов подключения её к проекту.
Размер проекта до импорта библиотеки
Проанализируем размер файлов проекта до импорта библиотеки.
Размер файлов до использования Lodash
Теперь посмотрим на последствия нескольких способов импорта библиотеки в проект.
Размер проекта после использования разных способов импорта библиотеки
▍1. Традиционный импорт
Речь идёт об импорте библиотеки одним из следующих традиционных способов.
Первый:
import _ from ‘lodash’;
Второй:
const _=require('lodash');
Вот как это повлияет на итоговый размер файлов проекта.
Изменение размеров файлов проекта при импорте всего пакета
▍2. ES6-импорт
Здесь у нас, опять же, есть два варианта.
Первый:
import { get } from 'lodash';
Второй:
const { get }=require('lodash');
Взглянем на влияние такого импорта на размер файлов проекта.
Размеры файлов при импорте функции get с использованием деструктурирующего присваивания
Как видите, применение обоих вышеописанных подходов привело к увеличению размеров файлов примерно на 23 Кб. А это — весьма значительная прибавка, особенно учитывая то, что речь идёт об использовании единственной функции из библиотеки, в которую входит более 200 функций. В итоге оказывается, что размер бандла увеличился настолько, насколько можно было бы ожидать его увеличения в том случае, если бы в проекте использовались бы все эти 200 функций.
Может быть, 23 Кб — это не такая уж и большая цена за использование единственной нужной функции? Нет, это — слишком много.
Есть ли какой-нибудь способ, используя который, можно импортировать в проект только то, что нужно? Да, такой способ есть.
Проанализируем папку, в которой хранятся материалы Lodash.
Для этого достаточно перейти по пути node_modules/lodash. В этой папке можно найти множество файлов, в которых хранится код отдельных функций. Среди них несложно найти файл get.js, в котором находится код интересующей нас функции get. А это значит, что если нам нужна только функция get — достаточно импортировать в проект лишь этот файл. Это ведёт нас к третьему способу импорта.
▍3. Импорт файла get.js из Lodash
Тут, снова, доступны два способа.
Первый:
import get from 'lodash/get';
Второй:
const get=require('lodash/get');
Взглянем на изменение размеров бандла.
Размеры файлов при импорте файла get.js
Видно, что благодаря тому, что мы импортировали в проект только файл get.js, мы смогли избавиться от более чем 20 Кб ненужного кода, попадающего в бандл при использовании других методов. А ведь речь идёт лишь об одном пакете. В типичном JavaScript-проекте гораздо больше зависимостей. Представьте себе то, как осторожный подход к импорту пакетов и постоянный контроль размеров бандла могут повлиять на некий серверный или клиентский проект.
Все ли пакеты поддерживают выборочный подход к импорту?
Нет, не все. Это полностью зависит от организации файлов пакета. Но, к счастью, большинство достаточно больших популярных пакетов структурированы так, что, работая с ними, несложно организовать выборочный импорт их отдельных возможностей.
Как сделать то же самое, работая с другими библиотеками?
Процесс удаления неиспользуемого кода известен как «встряска дерева» (tree shaking). Если нужно, например, «встряхнуть дерево» библиотеки Ant Design — поищите в интернете по словам «antd tree shaking». Вы вполне можете найти обсуждение этого вопроса на StackOverflow или на GitHub. Найдя такое обсуждение — просмотрите его — вполне возможно, кто-то уже решил стоящую перед вами задачу.
Ещё один способ избавления от ненужного кода требует приложить немного больше усилий. Нужно зайти в папку пакета, находящуюся в node_modules, и заняться анализом кода. В частности, следует поинтересоваться структурой проекта и узнать, разбит ли он на небольшие модули, которые можно импортировать в проект независимо друг от друга.
Пример оптимизации импорта при использовании пакета antd
До:
import { Menu} from 'antd';
После:
import Menu from 'antd/es/menu'; import 'antd/es/menu/style/css';
Пример оптимизации импорта при использовании пакета material-ui
До:
import { Button } from '@material-ui/core';
После:
import Button from '@material-ui/core/Button';
Пример оптимизации импорта при использовании пакета moment
Оптимизации импорта библиотеки moment выглядит немного сложнее. Поэтому, если вам это нужно, взгляните на задачу, которую я создал в трекере задач этой библиотеки.
Итоги
Вы сами можете убедиться в том, что в ваши проекты попадает немало ненужного кода. Решить эту проблему можно, внимательно сопоставляя то, что именно вы импортируете, с тем, что именно используете. Это позволит вам, например, сократить время загрузки ваших сайтов. Поэтому рекомендуется внимательно следить за размерами бандлов проектов и оптимизировать импорт зависимостей.