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

Итоги

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

Вы пропустили

AEGIS Algorithms Android Angular Apache Airflow Apache Druid Apache Flink Apache Spark API API Canvas AppSec Architecture Artificial Intelligence Astro Authentication Authorization AutoGPT AWS AWS Aurora AWS Boto3 AWS EC2 AWS Lambda Azure Babylon.js Backend bash Beautiful Soup Bento UI Big Data Binary Tree Browser API Bun Career Cassandra Charts ChatGPT Chrome Extension Clean Code CLI ClickHouse Coding Codux Combine Compose Computer Context Fusion Copilot Cosmo Route CProgramming cron Cryptography CSS CTF Cypress DALL-E Data Analysis Data science Database dbt dbt Cloud deno Design Design Patterns Detekt Development Distributed Systems Django Docker Docker Hub Drizzle DRY DuckDB Express FastAPI Flask Flutter For Beginners Front End Development Game Development GCN GCP Geospatial Git GitHub Actions GitHub Pages Gitlab GMS GoFr Golang Google Google Sheets Google Wire GPT-3 GPT3 Gradio Gradle Grafana Graphic Design GraphQL gRPC Guidance HMS Hotwire HTML Huawei HuggingFace IndexedDB InfoSec Interview iOS Jackknife Java JavaScript Jetpack Compose JSON Kafka Kotlin Kubernetes LangChain Laravel Linux LlaMA LLM localStorage Logging Machine Learning Magento Math Mermaid Micro Frontends Mobile Mobile App Development mondayDB MongoDB Mongoose MySQL Naming NestJS NET NetMock Networks NextJS NLP Node.js Nodejs NoSQL NPM OOP OpenAI OTP Pandas PDF PHP Playwright Plotly Polars PostgreSQL Prefect Productivity Programming Prometheus Puppeteer Pushover Python Pytorch Quarkus Rabbitmq RAG Ramda Raspberry Pi React React Native Reactor Redis REST API Revolut Riverpod RProgramming Ruby Ruby on Rails Rust Scalene SCDB ScyllaDB Selenium Servers Sklearn SLO SnowFlake Snowkase Software Architecture Software Development Solara Solid Spring Boot SQL SQLite Streamlit SudoLang Supabase Swift SwiftUI Tailwind CSS Taipy Terraform Testing Transformers TURN TypeScript Ubuntu UI Design Unix UX UX Design Vim Vite VSCode Vue Web Architecture Web Components Web Development Web Frameworks Web Scraping Web-разработка Webassembly Websocket Whisper Widgets WordPress YAML YouTube Zed Наука о данных Разное Тренды

Современный подход к разработке с использованием Next.js