Node.js 24: ESM CJS интероп с async/await в глобальных скриптах для микросервисов
-

Node.js 24 приносит крутые улучшения в интероп между ESM и CJS, особенно с async/await в глобальных скриптах. Это решает боли микросервисов, где разные либы на разных модулях мешают быстрому старту и деплою.
Теперь можно миксовать ESM с top-level await и CJS без танцев с бубном. Зачем это нужно? В микросервисах часто стартуют скрипты на лету - инициализация баз, коннекты к очередям. Без правильного интеропа стейт улетает, рендер тормозит, а дебаг становится адом.
Top-level await в глобальных скриптах: как это работает
В Node.js 24 глобальные скрипты наконец-то поддерживают top-level await без флагов. Раньше в CJS приходилось оборачивать всё в async IIFE, что раздувало бойлерплейт. Теперь пишешь await прямо в корне - и скрипт ждёт разрешения промисов перед выполнением остального кода.
Представь микросервис, который на старте грузит конфиг из БД и коннектится к Redis. С top-level await это выглядит чисто: await db.connect(), await redis.ping(). Нет лишних then-чейнов, стейт сразу готов. Но есть нюанс - если промис не резолвится, процесс выходит с кодом 13. Идеально для health-check’ов в микросервисах.
Это открывает дверь для ESM-скриптов в CJS-контексте. Node проверяет: если скрипт синхронный (без top-level await), require() его тянет как ни в чём не бывало. С await - кидает ERR_REQUIRE_ASYNC_MODULE и советует dynamic import().
Ключевые сценарии использования:
- Инициализация микросервиса: await loadConfig(), потом спавн воркеров.
- Глобальные скрипты без package.json - просто node script.js с await внутри.
- Микс с CJS-библиотеками: require(‘express’) рядом с import(‘node:fs/promises’).
Ситуация CJS (до 24) ESM в Node 24 Top-level await Async IIFE Прямо в корне Require ESM Только sync Dynamic import() Глобальный скрипт Callback hell Чистый await Интероп ESM <-> CJS: динамические импорты и хуки
Интероп в 24-й версии стал умнее - require() теперь тянет ESM-модули, если они без top-level await. Добавили поддержку module.exports в ESM через require(esm). Для микросервисов это киллер-фича: express (CJS) + node-fetch (ESM-only) в одном скрипте без боли.
Пример: в CJS-скрипте лениво импортишь ESM-либу через dynamic import. const fetch = (…args) => import(‘node-fetch’).then(({default: f}) => f(…args)). Потом в роуте await fetch(url). Никаких register.js хуков, просто работает. В ESM наоборот - require(‘express’) через хук или syncBuiltinESMExports.
В микросервисах это упрощает прокси-серверы: тянешь CJS-роутеры и ESM-клиенты для внешних API. Node 24 фиксит race conditions в async контексте с AsyncContextFrame - стейт не теряется в nested awaits.
- Ленивый импорт CJS в ESM: const express = (await import(‘express’)).default;
- Хук для require в ESM: node --import ./register.js app.js - register перехватывает require.
- Параллельные задачи: const [db, redis] = await Promise.all([import(‘db’), import(‘redis’)]).
const express = require('express'); const fetch = (...args) => import('node-fetch').then(({default: f}) => f(...args)); app.get('/proxy', async (req, res) => { const data = await fetch(req.url); res.json(data); });Async/await в микросервисах: от старта к production
Async/await с интеропом делает микросервисы быстрее на старте. В глобальных скриптах await config() блокирует до готовности, потом спавнишь child_process. Node 24 улучшил AsyncLocalStorage - контекст не рвётся в цепочках await через воркеры.
Пример для микросервиса: топ-левел await db.connect() + await queue.ready(), затем app.listen().CJS-роутеры require()'ятся синхронно, ESM-сервисы - динамически. Нет нужды в --experimental флагах, всё stable.
Проблемы решает: race conditions в тестах (теперь subtests ждут автоматически), замедленный бандл от микса модулей. В production - меньше ошибок на деплое, когда один сервис CJS, другой ESM.
Проблема Решение в 24 Выгода для микросервисов Top-level await в CJS Dynamic import Чистый код инициализации ESM require Sync для без-await Быстрый старт скриптов Context leak AsyncContextFrame Трейсинг без потерь - Массив с await: Promise.all(arr.map(async v => await process(v))) вместо filter/reduce хаков.
- Глобальный emitter: module.exports = new EventEmitter(); setTimeout(() => emit(‘ready’));
- Spawn с модулями: spawn(node, [‘–input-type=module’, ‘await init()’]).
Глобальные скрипты на стероидах: что дальше
В Node.js 24 глобальные скрипты с async/await - это новый стандарт для микросервисов. Интероп ESM-CJS убирает барьеры, dynamic import делает код ленивым и эффективным.
Осталось протестировать WebSocketStream и SQLite-cache в интероперах - для streamable микросервисов это огонь. Подумать стоит над миграцией legacy CJS на ESM с хуками, особенно в кластерах.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.