Живой ли пакет: шесть вопросов вместо звёзд и загрузок

модули≈ 12 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Каждый пакет из npm — обещание жить с ним годами, а звёзды и загрузки на вопрос «жив ли он» не отвечают: растёт весь реестр, и растут даже пакеты, которые сами авторы давно держат в режиме поддержки. В статье про npm выбор уместился в минуту на страницу пакета; здесь та минута разложена на шесть вопросов по порядку и функцию, которая по записанному снимку реестра отвечает на первые три. Снимок: сентябрь 2026.

Разделы темы:·

Первый отсев — не про пакет, а про задачу. Многие популярные пакеты появились, когда в языке и браузерах нужного не было. Встроенное с тех пор догнало, а строки в package.json остались.

Зачем раньше ставили пакетЧем это закрыто сейчасBaseline
глубокая копия объектаstructuredClone(value)Widely
случайный идентификатор UUID v4crypto.randomUUID()Widely
группировка списка по признакуObject.groupBy(list, fn)Widely
уникальные значенияnew Set(list)Widely
показ даты человекуIntl.DateTimeFormatWidely
запросы к серверувстроенный fetchWidely

Последний столбец — статус Baseline, отметка на страницах справочника MDN: работает ли возможность во всех основных браузерах. Newly — во всех, но меньше 30 месяцев, Widely — во всех и дольше 30 месяцев, Limited — не во всех. Если вы читали пролог, мерило знакомо по статье о том, почему у HTML нет номера версии.

Отсюда правило, записанное в учебнике один раз — здесь. Widely — пакет не нужен. Newly — берут встроенное с оговоркой про старые устройства: у части пользователей браузер старее, чем кажется. Limited — библиотека допустима, но с пометкой «уйдёт, когда…».

Все шесть строк таблицы — Widely, и пакет для этих задач не проходит нулевой вопрос, каким бы живым он ни был. Оговорка одна: встроенное закрывает типичный случай, а не любой. structuredClone не копирует функции, а Intl.DateTimeFormat показывает дату, но не разбирает строки и не считает «через два месяца». Поэтому вопрос звучит так: умеет ли платформа то, что нужно моей задаче.

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

Следующий вопрос — выпускает ли пакет кто-нибудь. Ответ лежит в реестре, и npm view достаёт его в терминале. Спросим про idb — маленькую обёртку над хранилищем браузера IndexedDB, moment — библиотеку дат и request — HTTP-клиент для Node:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал, npm 11.17.0, 15.09.2026
$ npm view idb version time.modified deprecated --json
{
"version": "8.0.3",
"time.modified": "2025-05-07T08:12:54.691Z"
}
$ npm view moment version time.modified deprecated --json
{
"version": "2.31.0",
"time.modified": "2026-09-15T01:07:26.443Z"
}
$ npm view request version time.modified deprecated --json
{
"version": "2.88.2",
"time.modified": "2026-07-17T17:10:13.431Z",
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142"
}

У idb поле time.modified приходится на день последнего выпуска, у moment — тоже: 15 сентября 2026 года вышла версия 2.31.0 с исправлением уязвимости, первая с декабря 2023-го. А у request там 17 июля 2026 года, хотя версия latest вышла 11 февраля 2020-го: реестр правил метаданные пакета, а выпуска не было. Поэтому возраст считают по дате выпуска версии latest — полю time[версия], — а не по time.modified, которое двигает любая правка метаданных.

У того же request стоит пометка deprecated: автор сам снял пакет с поддержки и написал об этом в реестре. Это прямой отказ, дальше спрашивать не о чем.

idb — пример другого рода: законченный не значит брошенный. Маленькая библиотека может молчать годами, потому что в ней нечего чинить, и по датам этого не отличить. Отличает человек: открывает issues и смотрит, отвечает ли сопровождающий. Функция в конце статьи такой пакет не отвергает, а передаёт вам. Один сопровождающий — тоже не отказ, а пометка о риске: уйдёт он, и заменить его будет некому.

Тег latest ставит автор, и npm install без версии берёт именно его. Обычно тег стоит на последней стабильной версии, но не обязан:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал, npm 11.17.0, 14.09.2026
$ npm view prisma dist-tags.latest
8.0.0-rc.15
$ npm view prisma time --json | grep -E "\"(7\.10\.0|8\.0\.0-rc\.15)\""
"7.10.0": "2026-08-25T12:47:51.307Z",
"8.0.0-rc.15": "2026-09-14T07:01:35.643Z",

У prisma на latest стоит кандидат в выпуск — суффикс -rc значит release candidate, — а последняя стабильная версия вышла тремя неделями раньше. npm install prisma без версии принесёт в dependencies кандидата. Проверяют это до установки: смотрят dist-tags и нет ли в номере версии дефиса с суффиксом.

grep оставляет из длинного списка дат только строки с нужными версиями, и он здесь не для красоты: короткая форма npm view prisma time.7.10.0 печатает пустоту, потому что точки в имени поля npm читает как вложенность.

Дата правки и дата выпуска — разные поля. Возраст пакета считают по выпуску версии latest, а time.modified двигает любая правка метаданных.

Загрузки кажутся самым честным числом: миллионы установок в неделю не могут ошибаться. Сравним одну и ту же неделю сентября у moment в 2026 и 2025 году. Числа отдаёт сервис api.npmjs.org, и curl запрашивает его из терминала так же, как fetch — из кода:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал, api.npmjs.org, 14.09.2026
$ curl -s https://api.npmjs.org/downloads/point/2026-09-07:2026-09-13/moment
{"downloads":25428467,"start":"2026-09-07","end":"2026-09-13","package":"moment"}
$ curl -s https://api.npmjs.org/downloads/point/2025-09-07:2025-09-13/moment
{"downloads":24746165,"start":"2025-09-07","end":"2025-09-13","package":"moment"}

Рост есть — примерно ×1,03. Но растёт весь npm: у типичного популярного пакета загрузки за тот же год выросли в разы, и во сколько именно — записано константой MEDIAN_GROWTH в функции ниже, с датой расчёта. На таком фоне три процента — не рост, а отставание: доля пакета в нише падает, хотя число растёт.

Абсолютные загрузки врут по двум причинам. Первая — транзитивные установки: пакет скачивают и те, кто его не выбирал, а поставил что-то, что тянет его за собой. next, например, несёт библиотеку обработки картинок sharp в своём списке optionalDependencies.

В разговоре о двух списках зависимостей были только dependencies и devDependencies, а на полках встретятся ещё два. peerDependencies — «работаю рядом с таким-то пакетом такой-то версии, но в себе его не везу»: npm 7 и новее ставит такую пару сам, а если версии не сходятся, установка может остановиться с ошибкой. optionalDependencies — «поставить, если получится»: неудачная установка такого пакета не роняет npm install. Формулировки — по документации npm. Остальное в package.json — поля type и exports, диапазоны версий, lock-файл — подробно разбирает статья «package.json как договор» в части VI.

Вторая причина — автоматика: сборки в CI и агенты, которые сами пишут код и сами добавляют в него зависимости. Сопровождающие moment в заметке о статусе проекта пишут, что автоматические сценарии делают счётчик загрузок ещё менее точным, и связывают основную часть нового роста с агентами. Сам проект они при этом называют legacy в режиме поддержки.

Поэтому смотрят не на «сколько миллионов», а на долю внутри ниши — среди пакетов, решающих ту же задачу, — и на направление. Грубая машинная замена доли — сравнить рост пакета с медианным ростом популярных пакетов: ×1,2 и ниже при медиане в разы больше значит, что ниша уходит от пакета.

Загрузки меряют установки, а не выбор людей. Рост сравнивают с ростом соседей, а не миллионы с миллионами.

Цена пакета — не размер его папки в node_modules, а то, что сборщик положит в бандл. Как разбиралось в статье о статичности импортов, неиспользованное выкидывается только при подходящей форме import. Поэтому одна библиотека с двумя разными строками import может весить в разы по-разному — и не всегда в ту сторону, которую подсказывает интуиция. Цифры такого случая — на полке браузера, в разделе о проверке данных.

Вторая часть цены — лицензия. В поле license реестра обычно стоит короткое имя вроде MIT, но бывает и строка SEE LICENSE IN LICENSE — ссылка на собственный текст внутри пакета. Условия в таком тексте бывают какими угодно, поэтому их читают до установки.

На телефоне цена считается иначе: важны не столько килобайты, сколько другое — заработает ли пакет в готовом приложении для разработки, которое берёт у проекта только JavaScript, или ради него придётся собирать своё приложение вместе с кодом для iOS и Android. Это разобрано на полке React Native и Expo.

И безопасность: каждая зависимость — чужой код с правами вашего проекта, и истории left-pad и event-stream из статьи про npm — не древность.

Реестр помнит больше, чем отдаёт. Вот что осталось от конца марта 2026 года у HTTP-клиента axios:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал, npm 11.17.0, 14.09.2026
$ npm view axios time --json | grep -E "\"(1\.14\.[01]|0\.30\.4)\""
"1.14.0": "2026-03-27T19:01:40.915Z",
"1.14.1": "2026-03-31T00:21:58.168Z",
"0.30.4": "2026-03-31T01:00:57.285Z",
$ npm view axios versions --json | grep -c "\"1.14.1\""
0

В time, где записаны даты всех выпусков, есть две версии от 31 марта, а в versions, списке того, что можно установить, их нет. В эти версии попал вредоносный код, и их сняли; предупреждение GitHub GHSA-fw8c-xr5c-95f9 вышло меньше чем через три часа после первой из них. Отсюда два вывода: package-lock.json с уже проверенными версиями коммитят, а свежий выпуск популярного пакета не спешат ставить в первые же часы.

Цена пакета — то, что уедет к пользователю, условия лицензии и требования к клиенту, а не папка в node_modules.

Пусть даты в проекте показывает библиотека. Если её импортирует единственный ваш модуль dates.js и отдаёт наружу две свои функции, formatDue и addMonthsSafe, то для смены библиотеки правят один файл, а сотню остальных даже не открывают. Если же те же вызовы разбросаны по сотне файлов, библиотека уже стала частью устройства проекта, и заменить её — значит переписать проект. Это и есть вопрос 4: меняется ли зависимость на границе.

Некоторые пакеты за границей не удержать по самой их природе. В следующих частях встретятся четыре таких рода: стили, записанные классами прямо в разметку; своя модель данных на всё приложение; отдельный язык схемы с шагом генерации кода; чужой сервер, где лежат данные ваших пользователей. Каждый бывает оправдан, но каждый превращает смену пакета из правки в переезд.

Противоположность — общий интерфейс на границе, который понимают сразу несколько библиотек. Пример — Standard Schema: его реализуют библиотеки проверки данных, и код, принимающий схему, не знает, какая библиотека за ней стоит. Разобран он на полке React и Next, в разделе о формах.

Честная оговорка: этот вопрос совпадает с укладом автора учебника. Поэтому на каждой полке есть раздел «Не на полке и почему» — с причиной, которую можно проверить, а не со вкусом.

Пакет, который живёт за одним вашим модулем, меняется за вечер. Пакет, вызовы которого разошлись по всему проекту, меняется переездом.

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

Своя сборкаГде стенаЧто за ней
console.assert в первых тестахтесты хочется держать в отдельных файлах, с названиями и отчётом по всему наборутест-фреймворки Jest и Vitest
сеттеры Date в разделе «Прибавить день»31 января плюс месяц уезжает в март, а «через два месяца» с месяцами разной длины и чужие часовые пояса своими строками уже не пишутсябиблиотеки дат вроде date-fns, в самом языке — Temporal
render() из состояния в проекте части IIпересборка разметки целиком сбрасывает набранный текст и фокусReact, Vue и их родня

Отсюда правило полок: пакет попадает на полку либо со ссылкой на свою сборку в учебнике, либо с фразой «своей сборки в учебнике нет, потому что…».

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

На вопросы 0–2 отвечают данные, и их можно доверить функции. Ниже — снимок шести пакетов: дата выпуска версии latest, пометка deprecated и загрузки за одну и ту же неделю сентября 2026 и 2025 года. Живых запросов в коде нет — только выдержки из реестра.

Ниши знать заранее не нужно, хватит фразы о каждом пакете. uuid делает случайные идентификаторы. request — HTTP-запросы в Node, где теперь есть встроенный fetch. moment — даты. idb — маленькая обёртка над хранилищем браузера IndexedDB из части II. formik — формы в React, до которых дойдёт часть IV. zod — проверка данных, она появится в части III. Прогноз: на каком вопросе остановится каждый из шести?

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Порядок решает. uuid жив и растёт, но отсеян на нулевом вопросе: для идентификаторов v4 хватает crypto.randomUUID(), а нужен ли пакет ради других версий UUID, которых в платформе нет, решает задача. request остановился на первом вопросе по deprecated, и загрузки его уже не спасают. Поле platform у него пустое, хотя в Node теперь есть встроенный fetch: хватит ли его, зависит от того, что делает ваш код, а пометка автора закрывает вопрос для любого проекта.

Самое интересное — moment и formik. Машинный вердикт у них один: по датам оба живы, а ниша уходит — рост ×1.03 и ×1.02 при медиане ×2.69 функция посчитала сама. Но свежая дата у moment — выпуск 2.31.0, который закрыл уязвимость, а сопровождающие по-прежнему называют проект legacy — в заметке о статусе и в шапке документации. Вопрос 1 смотрит только на дату, а выпуск с исправлением проходит его так же, как выпуск с новыми возможностями, — поэтому проект в режиме поддержки отсеялся только на втором вопросе. idb остался на первом вопросе с вердиктом «решает человек»: маленькую обёртку проверяют по issues. zod прошёл три вопроса, и это ещё не «брать»: дальше цена, устройство проекта и своя сборка — на них функция не отвечает.

TODAY — константа, а не new Date(): с датой из часов у idb через месяц прибавился бы месяц тишины при прежних загрузках, а с 10 декабря formik перешёл бы со второго вопроса на первый — вывод поплыл бы. Двигает его и сам реестр: снимок днём раньше, от 14.09.2026, остановил бы moment на первом вопросе — 32 месяца без выпуска, — а на второй его перевёл выпуск 2.31.0. С константой снимок остаётся правдой о 15.09.2026 навсегда — так же, как листинг с датой.

Функция отвечает только на то, что видно в данных. Законченный пакет от брошенного, цену и устройство проекта отличает человек.

Советов «берите такой-то пакет» в этой статье нет намеренно: версии и цифры стареют за год, а метод — нет. Имена с версиями и датой проверки живут на трёх полках: браузера — в части II, React и Next — в части IV, React Native и Expo — в части V.

Каждая строка полки отвечает на вопросы этой статьи: когда пакет не нужен и что умеет платформа, где своя сборка, снимок с версией, весом или ценой на дату проверки — и пометка «Пересмотреть, если…», условие, при котором строку пора переписать. Скрипт, который сам спрашивает реестр о зависимостях проекта, появится позже — в части VI, когда код выйдет из браузера в Node.

Метод — на годы, имена — на год. Поэтому у имён на полке есть дата, а у шести вопросов её нет.

  • Шесть вопросов идут по порядку, и первый «нет» отвечает за весь пакет: платформа, живость, доля в нише, цена, устройство проекта, своя сборка.
  • Вопрос 0 — про задачу: у Widely пакет не нужен, у Newly берут встроенное с оговоркой, у Limited библиотека допустима с пометкой «уйдёт, когда…».
  • Возраст считают по выпуску версии latest (time[версия]), а не по time.modified; deprecated — прямой отказ; тег latest бывает предварительной версией. Законченный пакет от брошенного отличает человек.
  • Загрузки растут у всего реестра — от транзитивных установок, CI и агентов, поэтому рост сравнивают с медианой: ×1,2 и ниже значит, что ниша уходит.
  • Цена — вес после сборщика, условия лицензии и требования к клиенту на телефоне; хорошая зависимость меняется на границе одного вашего модуля, а своя сборка показывает стену, за которой она нужна.
  • Функция отвечает на вопросы 0–2 по датированному снимку, остальные три — ваши; имена с версиями и датой проверки живут на полках частей II, IV и V.

1/4Пакет повторяет возможность, у которой в Baseline стоит Newly. Как поступить по правилу вопроса 0?

2/4У пакета time.modified — вчерашнее число, а версия latest вышла три года назад. Сколько пакет живёт без выпуска?

3/4Загрузки пакета за год выросли в 1,1 раза, а медиана роста популярных пакетов — 2,69. Что это значит?

4/4Функция health вернула для пакета «вопросы 0–2 пройдены». Что дальше?

Строка для dayjs

Добавьте в снимок ещё одну библиотеку дат — dayjs. Значения сняты тем же днём, что и остальные: выпуск версии latest — 17.08.2026, загрузки за неделю 07–13.09.2026 — 51 400 889, за ту же неделю год назад — 28 316 560. Сначала предскажите строку, которую напечатает функция, потом проверьте запуском и сравните dayjs с moment.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

В снимок добавляется одна строка:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Функция напечатает dayjs — вопросы 0–2 пройдены: дальше цена, устройство проекта и своя сборка. Встроенной замены в снимке нет — Intl.DateTimeFormat только показывает дату, — выпуск меньше месяца назад, рост ×1,82. Это ниже медианы ×2,69, но выше порога ×1,2.

Интереснее сравнение внутри ниши. У moment за тот же год рост ×1,03 — функция остановила его на втором вопросе, — у dayjs ×1,82: оба пакета решают одну задачу, и доля dayjs в ней растёт. Именно это и называется «доля в нише, а не загрузки»: по свежим выпускам и миллионам в неделю оба пакета выглядят живыми.

Возраст по дате правки

В снимке появилось поле modified — значение time.modified из того же npm view, — а к шести пакетам добавился седьмой: react-scripts, сборка React-приложения из Create React App, до которого дойдёт часть IV. Сделайте ошибку, от которой предостерегает раздел о вопросе 1: пусть health считает месяцы тишины от modified, а не от released. Какой пакет «оживёт», на каком вопросе он теперь остановится и почему этот ответ всё равно получен неправильно?

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Меняется одна строка в health:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Оживает react-scripts. Правка метаданных 10.04.2026 превращает 53 месяца тишины в пять, пакет проходит первый вопрос и падает на втором: react-scripts — вопрос 2: рост ×0.63 при медиане ×2.69 — ниша уходит.

Остальные строки не меняются. У uuid, moment, idb, formik и zod правка пришлась на день выпуска. У request дата правки тоже свежая, июль 2026 года, но функция отсеяла его по deprecated раньше, чем дошла до дат.

Вердикт у react-scripts по-прежнему отрицательный, но причина подменена: вместо «давно не выпускался — решает человек» функция говорит «жив, но теряет долю». С таким ответом легко не заглянуть в блог React, где 14 февраля 2025 года Create React App объявили устаревшим для новых приложений. Ошибка в выборе поля не всегда меняет итог — она меняет, какой вопрос вы себе зададите.

Первый «нет»

Для каждого случая назовите вопрос, на котором пакет остановится, и что делать дальше: отказаться, передать решение человеку или идти к следующему вопросу.

№Случай
1пакет глубокого копирования объектов: выпуск на прошлой неделе, загрузки за год выросли втрое
2библиотека графиков: последний выпуск полтора года назад, на issues отвечают в тот же день, рост ×2,4
3пакет для чтения файлов Excel с пометкой deprecated и ростом ×3
4компонент таблицы с сортировкой и фильтрами: свежий выпуск, рост ×4 и строка SEE LICENSE IN LICENSE в поле license

1. Вопрос 0. structuredClone — Widely, и свежесть с ростом тут ничего не меняют. Пакет нужен, только если задача требует того, чего structuredClone не умеет, — например, копировать функции.

2. Вопрос 1, но не отказ: полтора года без выпуска — машинный ответ «решает человек». Человек смотрит issues, видит живого сопровождающего и идёт дальше. Вопрос 2 пакет тоже проходит: ×2,4 — выше порога ×1,2 и близко к медиане. Дальше — цена.

3. Вопрос 1, прямой отказ. Встроенного разбора Excel нет, так что нулевой вопрос пакет проходит, но пометку deprecated рост не отменяет — скорее всего, пакет скачивают транзитивно или по старым инструкциям.

4. Вопросы 0–2 пройдены: сортировать и фильтровать таблицу браузер сам не умеет, выпуск свежий, рост выше медианы. Остановка на вопросе 3 — цене. Текст лицензии читают до установки: в нём могут быть условия, которых у MIT нет.