Node.js 26: объяснение Temporal API, Map Upserts и Undici 8
Объясняются основные изменения в Node.js 26, касающиеся бэкенда: стабильный API Temporal, встроенные методы обновления элементов в структуре Map, улучшения производительности благодаря Undici 8, а также изменения, требующие проверки перед обновлением.
Источники: 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_MODULE_VERSION 147, которые потребуют пересборки.Temporal.PlainDate и Temporal.Instant, избегая при этом использования сторонних библиотек для обработки дат в новых проектах.Заключение
Основная цель Node.js 26 проста: сократить количество препятствий и погасить годы накопленной наследственной задолженности.
Благодаря нативной и надежной арифметике дат через Temporal, более эффективной обработке распространенных структур данных с использованием операций вставки/обновления в Map, встроенной функции очистки кода на TypeScript и более высокой скорости обработки HTTP-запросов благодаря Undici 8, это обновление среды выполнения позволяет создавать более быстрые системы backend, используя меньше внешних пакетов.
Node.js значительно созрел. Эпоха, когда для работы с датой из календаря или совпадения с шаблоном маршрута приходилось использовать десятки вспомогательных библиотек, на данный момент практически закончилась.