Хватит импортировать JavaScript-пакеты целиком

Часто ли вам доводилось импортировать в проект целый 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
Размер файлов до использования Lodash

Теперь посмотрим на последствия нескольких способов импорта библиотеки в проект.

Размер проекта после использования разных способов импорта библиотеки

▍1. Традиционный импорт

Речь идёт об импорте библиотеки одним из следующих традиционных способов.

Первый:

import _ from ‘lodash’;

Второй:

const _ = require('lodash');

Вот как это повлияет на итоговый размер файлов проекта.

Изменение размеров файлов проекта при импорте всего пакета
Изменение размеров файлов проекта при импорте всего пакета

▍2. ES6-импорт

Здесь у нас, опять же, есть два варианта.

Первый:

import { get } from 'lodash';

Второй:

const { get } = require('lodash');

Взглянем на влияние такого импорта на размер файлов проекта.

Размеры файлов при импорте функции get с использованием деструктурирующего присваивания
Размеры файлов при импорте функции 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

Видно, что благодаря тому, что мы импортировали в проект только файл 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 выглядит немного сложнее. Поэтому, если вам это нужно, взгляните на задачу, которую я создал в трекере задач этой библиотеки.

Итоги

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