Как удаление isNumber из зависимостей сэкономил 440 Гб еженедельного трафика

3bed72abd10f1398bdf98f21e79cfb47

Недавно наткнулся на занимательный merge request по замене зависимости isNumber. Удивительно было в целом осознавать, что как такого универсального метода по определению числа в переменной нет в базовой концепции JavaScript. И данная проблема породила npm-репозиторий isNumber c почти 72 миллионами еженедельных скачиваний на сентябрь 2024 года. Но стоит ли в очередной раз использовать мизерную зависимость в своём проекте? Предлагаю взглянуть на решение, представленное в ранее сказанном mr-е.

Разбор решения

Концепция isNumber проста: функция, в аргументе которого должна быть определяемая переменная, должна вывести нам true или false; true если да, false если нет. Всё просто!

Вот так выглядит данная функция в реализации:

const isNumber = (v) => (typeof v === "number" && v - v === 0) || (typeof v === "string" && Number.isFinite(+v) && v.trim() !== "");

Выглядит тяжко, не так ли? Давайте разберём каждый участок кода и выясним, решает ли эта функция нашу задачу.

Функция принимает аргумент v(variable), с которым и предстоит работать.

Суть работы заключается по-большей части в том, чтобы отловить любую выдачу типа number в данной переменной.

Первая часть проверяет напрямую числовое значение (даже тот же Infinite)

  1. С typeof v === "number" всё вполне ясно. Прямая проверка типа

  2. v - v === 0 будет возвращать true в случае с числом, так как если бы мы работали, например, со строкой, то такое выражение возвращало бы NaN. Но в случае с массивами это бы не прокатило и данное выражение выдало бы нам тоже 0. Двигаемся дальше.

Вторая часть проверяет строки, которые могут быть преобразованы в числа.

  1. typeof v === "string" проверяет, является ли переменная v строкой.

  2. Number.isFinite(+v) — здесь строка v приводится к числу с помощью унарного оператора +. Если после преобразования строка является конечным числом (то есть не NaN, не Infinite), то Number.isFinite вернёт true. Это важно для того, чтобы отсеивать строки, которые не могут быть корректно преобразованы в числа (например, "abc" или пустые строки).

Унарный, то есть применённый к одному значению,  + ничего не делает с числовыми значениями. Но если переменная не число, унарный плюс преобразует его в число.

  1. v.trim() !== "" — эта проверка удаляет пробелы в начале и в конце строки и проверяет, что строка не пуста. Это нужно для того, чтобы отсеивать строки, состоящие только из пробелов, которые могут быть преобразованы в 0, но на практике не являются корректными представлениями чисел.

Общий взгляд

Подытоживая мы видим две части, которые выполняют следующие задачи:

  1. Проверка чисел (typeof v === "number" && v - v === 0): Важно не только проверять тип, но и исключать NaN, который имеет тип "number", но фактически не является числом. Проверка через v - v === 0 — это эффективный способ исключить NaN, так как для всех других чисел разница числа с самим собой всегда равна нулю.

  2. Проверка строк (typeof v === "string" && Number.isFinite(+v) && v.trim() !== ""): Строки, которые можно преобразовать в числа, часто встречаются в веб-разработке, особенно при работе с формами или API, где данные передаются в виде строк. Важно правильно обработать строки, которые могут быть преобразованы в числа, и отсеять пустые строки или строки, состоящие только из пробелов. Использование Number.isFinite позволяет проверить, является ли преобразованное значение конечным числом.

Так что же там с репозиторием?

Пакет to-regex-range, речь о котором шла в начале статьи имеет на момент создания merge request-а 43 миллиона скачиваний, а изначальный размер пакета составляет 33Kb. Важно учесть, что основная логика данного пакета состоит из одного среднего по размерам .js файла с парой зависимостей.

После замены пакета isNumber на собственное решение из зависимостей обнаружили, что to-regex-range стал легче на 10Kb. Но эта цифра кажется не играющей, но факт остаётся фактом, что по-итогу небольшое изменение привело к сокращению еженедельного трафика скачивания на 440 гигабайт, сократив с 1.5ТB до 1.0TB. Это уже звучит действительно внушительно.

Package size report
===================

Package info for "to-regex-range@5.0.1": 33 kB
  Released: 2019-04-07 06:04:37.03 +0000 UTC (277w2d ago)
  Downloads last week: 43,837,006
  Estimated traffic last week: 1.5 TB

Removed dependencies:
  - is-number@7.0.0: 10 kB (30.06%)
    Downloads last week: 43,875,245
    Downloads last week from "to-regex-range@5.0.1": 43,837,006 (99.91%)
    Estimated traffic last week: 440 GB
    Estimated traffic from "to-regex-range@5.0.1": 440 GB (99.91%)

Estimated package size: 33 kB → 23 kB (69.94%)
Estimated traffic over a week: 1.5 TB → 1.0 TB (440 GB saved)

© Habrahabr.ru