Главная / Статьи

Node.js 26: объяснение Temporal API, Map Upserts и Undici 8

Объясняются основные изменения в Node.js 26, касающиеся бэкенда: стабильный API Temporal, встроенные методы обновления элементов в структуре Map, улучшения производительности благодаря Undici 8, а также изменения, требующие проверки перед обновлением.

Издатель: REACTAPP.TOP7 мин чтения1678 слов

Источники: Node.js 26 Just Changed the Backend Runtime — Here’s What Actually Matters

Каждая крупная версия Node.js поставляется с длинным списком коммитов, обновлений версий и оптимистичными примечаниями к выпуску. Большинство команд бросают взгляд на ченджлог, пожимают плечами и откладывают обновление на год или два.

Node.js 26 нарушает эту тенденцию.

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

Если вы используете сервисы на Node.js в производстве, вот что действительно имеет значение, почему это влияет на вашу ежедневную работу и какие опасности следует учитывать перед обновлением.

1. Смерть объекта Date: стабильный API для работы с временем

Примерно в течение трех десятилетий разработчики JavaScript боролись с встроенным объектом Date. Он изменяем, месяцы инициализируются с нулевым индексом, парсинг строк ведет себя неконсистентно на разных платформах, а работа с часовыми поясами без тяжелых библиотек вроде Moment, Luxon или Day.js действительно представляет собой сложную задачу.

Node.js 26 включает Temporal API глобально по умолчанию, без необходимости использования каких-либо экспериментальных флагов.

Почему это важно?

Temporal предоставляет неизменяемые объекты значений, учитывающие часовой пояс. Он четко разделяет точный момент времени от календарной даты, времени по настенным часам и промежутка времени.

Известная ошибка планирования в бэкенде связана с арифметикой месяцев. В более ранних версиях Node добавление одного месяца к 31 января могло привести к февралю, поскольку в феврале не так много дней:

// Legacy JavaScript Date (Before Node.js 26)
const oldDate = new Date('2026-01-31T00:00:00Z');
oldDate.setMonth(oldDate.getMonth() + 1);
console.log(oldDate.toISOString());
// Output: 2026-03-03T00:00:00.000Z (Silently overflows into March!)

Temporal правильно соблюдает границы календаря:

// Node.js 26: Clean and predictable
const today = Temporal.PlainDate.from('2026-01-31');
const nextMonth = today.add({ months: 1 });
console.log(nextMonth.toString());
// Output: 2026-02-28 (Clamps correctly to the last day of February)

Точные временные метки в разных часовых поясах

При работе с счетами, оплатой подписок или логами аудита, охватывающими несколько регионов, требуется точный момент времени (Instant), который можно преобразовать в местное время по часам (ZonedDateTime):

// Current exact point in time (UTC)
const exactInstant = Temporal.Now.instant();
// Convert to a specific time zone seamlessly
const tokyoTime = exactInstant.toZonedDateTimeISO('Asia/Tokyo');
const londonTime = exactInstant.toZonedDateTimeISO('Europe/London');console.log(`Tokyo: ${tokyoTime.toString()}`);
console.log(`London: ${londonTime.toString()}`);

Больше нет необходимости увеличивать размер пакета или использовать сторонние библиотеки для вычисления циклов оплаты или отображения дат в разных часовых поясах.

2. Встроенные операции вставки/обновления в Map: getOrInsert и getOrInsertComputed

Кэши в памяти и счетчики ограничения частоты запросов — основа разработки бэкенда. Ранее для вставки ключа в Map только тогда, когда он отсутствовал, приходилось писать повторяющийся шаблон кода:

// The old, redundant lookup pattern
if (!cache.has(userId)) {
  cache.set(userId, fetchUserProfile(userId));
}
const profile = cache.get(userId);

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

Node.js 26 поставляется с V8 14.6, который включает стандартизированные методы добавления или замены элементов в Map от TC39:

  • map.getOrInsert(key, defaultValue)
  • map.getOrInsertComputed(key, computeFunction)

Практический пример: агрегатор запросов в памяти

const requestBuckets = new Map();
function trackUserRequest(userId, payload) {
  // If userId is missing, execute the callback and insert the array atomically
  const userQueue = requestBuckets.getOrInsertComputed(userId, () => []);

  userQueue.push(payload);
  return userQueue.length;
}

Функция, передаваемая в getOrInsertComputed, выполняется с опозданием — она запускается только тогда, когда ключ ещё не существует. Это позволяет избежать ненужного выделения объектов и упрощает логику кэширования в различных API-сервисах.

3. Высокая пропускная способность HTTP с Undici 8

Глобальная функция fetch() в Node работает на основе Undici, а Node.js 26 обновляет используемый движок HTTP до Undici 8.

Что изменилось внутри?

Старые конфигурации HTTP-клиентов склонны сталкиваться с проблемами при постоянной нагрузке, в основном из-за дополнительных затрат на управление пулом соединений и неэффективной обработки заголовков. Undici 8 решает эту проблему следующим образом:

  • Сниженное потребление памяти при передаче данных в формате стриминга: крупные JSON-данные или файлы теперь передаются в формате стриминга с гораздо меньшим количеством пауз для сборки мусора.
  • Лучшее использование механизмов HTTP/1.1 и пула соединений HTTP/2: по умолчанию связи типа keep-alive сохраняются дольше, что снижает время задержки при взаимодействии с внешними сервисами.
// Native fetch in Node.js 26 automatically inherits Undici 8 optimizations
const response = await fetch('https://api.internal.service/stream-data', {
  headers: { 'Connection': 'keep-alive' }
});
for await (const chunk of response.body) {
  processDataChunk(chunk); // Efficient backpressure and zero intermediate buffering
}

Если ваш сервис выступает в роли API-шлюза или направляет запросы к нескольким внешним микросервисам, это обновление на уровне движка снижает время задержки на уровнях p95/p99 без необходимости вносить изменения в код приложения.

4. Запуск TypeScript без нагрузки от транспиляции

Node.js постепенно изменяет способ загрузки модулей. Начиная с Node.js 26, удаление типов является полностью поддерживаемой и стабильной функцией, а ранее использовавшиеся экспериментальные флаги для включения трансформации были полностью устранены.

Теперь вы можете запускать файлы с расширением .ts непосредственно из командной строки:

node src/server.ts

Что на самом деле покрывает эта функция

Крайне важно четко разграничивать удаление типов и проверку типов:

Your TypeScript File
       │
       ▼
[ Node.js Type Stripper ]  ──> Removes interfaces, types, & generics in memory
       │
       ▼
[ Pure V8 Engine Execution ] ──> Runs the resulting JavaScript instantly
  • Она не выполняет проверку типов. Если в вашем коде числовое значение присваивается переменной, объявленной как строка, Node.js будет с радостью его запускать без ошибок. Для выявления подобных ошибок до выпуска продукта вам по-прежнему необходимо использовать tsc --noEmit в рамках вашей CI/CD-процедуры.
  • Он не справляется со синтаксисом, требующим фактической генерации кода. Структуры вроде декораторов старого стиля или свойств параметров (например, constructor(public name: string)) не поддерживаются инструментом удаления и приведут к ошибке.
  • Для сервисов, инструментов командной строки и фоновых процессов, созданных с использованием стандартного синтаксиса TypeScript, это означает, что вы можете исключить из своей локальной среды разработки tsx, ts-node или отдельный процесс мониторинга сборки.

    5. Изменения, которые необходимо проверить

    Поскольку Node.js 26 представляет собой значительное обновление версии, разработчики воспользовались этой возможностью, чтобы устранить устаревшие API, которые использовались в течение длительного времени.

    +------------------------------------+------------------------------------------+
    | Removed API                        | Modern Replacement                      |
    +------------------------------------+------------------------------------------+
    | http.Server.prototype.writeHeader  | res.writeHead()                          |
    | _stream_readable, _stream_writable | require('node:stream').Readable / Writable|
    | --experimental-transform-types     | Native type-stripping (built-in)        |
    +------------------------------------+------------------------------------------+
    

    Скрытый риск во внутренних механизмах потоков

    Вероятно, самым рискованным изменением в этой версии является удаление внутренних подмодулей потоковой обработки — _stream_readable, _stream_writable, _stream_transform и подобных внутренних файлов.

    Ряд старых пакетов в npm, особенно тех, которыми не занимались с 2020 года, игнорируют публичный интерфейс require('stream') и обращаются напрямую к этим внутренним компонентам:

    // BROKEN in Node.js 26: Throws ERR_MODULE_NOT_FOUND
    const Readable = require('_stream_readable');
    
    // CORRECT:
    const { Readable } = require('node:stream');
    

    Если хотя бы один компонент, скрытый глубоко в вашей иерархии зависимостей, по-прежнему использует модули _stream_*, ваше приложение сразу же завершит работу при запуске. Прежде чем пытаться обновиться, проверьте свой файл lockfile на наличие таких элементов:

    npm ls _stream_readable
    

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

    Стоит ли сейчас обновлять ваши производственные системы?

    Node.js следует схеме чередования версий между четными и нечетными основными версиями:

    • Нечетные версии (23, 25): краткосрочные версии, предназначенные для тестирования; они никогда не становятся LTS.
    • Четные версии (22, 24, 26): версии, предназначенные для использования в производстве.

    Node.js 26 стал «текущей» версией в мае, и он планируется перевести в режим активной долгосрочной поддержки к концу октября.

    [May 2026: Node 26 Current] ────> [October 2026: Node 26 LTS] ────> [Production Default]
              ▲                                    ▲
       Test in Staging/CI                 Migrate Production Workloads
    

    Практический план внедрения

    • Временно воздержитесь от развертывания на критически важных производственных системах. Дайте «текущей» версии время созреть, пока она официально не станет LTS в октябре. Если вам срочно нужно обновить производственную среду, Node.js 24 по-прежнему является текущим активным вариантом LTS.
  • Немедленно добавьте Node.js 26 в свою матрицу тестирования CI. Запустите полный набор тестов — модульные, интеграционные и полноценные тесты — на этой версии. Особое внимание уделяйте устаревшим импортам объектов типа stream или нативным дополнениям на C++, скомпилированным под NODE_MODULE_VERSION 147, которые потребуют пересборки.
  • Начните использовать Temporal во всех новых проектах, которые вы пишете. Помогите вашей команде освоить Temporal.PlainDate и Temporal.Instant, избегая при этом использования сторонних библиотек для обработки дат в новых проектах.
  • Заключение

    Основная цель Node.js 26 проста: сократить количество препятствий и погасить годы накопленной наследственной задолженности.

    Благодаря нативной и надежной арифметике дат через Temporal, более эффективной обработке распространенных структур данных с использованием операций вставки/обновления в Map, встроенной функции очистки кода на TypeScript и более высокой скорости обработки HTTP-запросов благодаря Undici 8, это обновление среды выполнения позволяет создавать более быстрые системы backend, используя меньше внешних пакетов.

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