Как Deno 2.x незаметно решил проблемы совместимости с Node и усталость от использования инструментов
В этой статье рассматриваются версии Deno с 2.0 по 2.9, показывается, как совместимость с npm, наборы разрешений и встроенные инструменты устранили те препятствия, которые раньше заставляли разработчиков отказываться от него.
Этот пробный запуск произошел пять лет назад.
В прошлом месяце для одного краткосрочного побочного проекта потребовался небольшой HTTP-сервер. Был создан новый каталог, выполнена команда deno init, и уже через десять минут появился рабочий сервер с встроенными TypeScript, тестами, инструментами форматирования и проверки кода. Не нужно было писать файл tsconfig, настраивать prettier, eslint или создавать файл jest.config.ts — также отсутствовал файл package.json.
При проверке версии оказалось, что это 2.9.3.
Оказывается, с октября 2024 года по июль 2026 года Deno выпустил десять минорных обновлений, каждое из которых тихо устраняло одну из проблем, из-за которых изначально отказались от использования этого инструмента. В то время ничего из этого не бросалось в глаза, потому что инструмент уже был отнесен к категории «хорошая идея, но еще не готова к реальному использованию», и не было причин снова на него смотреть.
Ниже приведен обзор того, что на самом деле изменилось, по одной старой проблеме за раз.
Жалоба 1: пакеты npm считались второсортными
Это была проблема, с которой сталкивались многие разработчики, включая меня. В Deno 1.x любые пакеты, не опубликованные на deno.land/x или не имевшие корректных URL ES-модулей, просто не работали. Разделение между экосистемой Deno и npm казалось неизбежным.
Deno 2.0, выпущенный в октябре 2024 года, полностью устранил этот разрыв. Теперь можно напрямую загружать любой пакет из каталога npm, насчитывающего более двух миллионов пакетов, с использованием спецификатора npm::
import express from "npm:express@5";
import { PrismaClient } from "npm:@prisma/client";
Если вы предпочитаете использовать package.json, это тоже поддерживается. Deno парсит его, загружает зависимости из реестра npm и даже может создать локальную папку node_modules, если включить соответствующее настройку. Частные реестры также работают через .npmrc, точно как в Node.
Статистика, которая окончательно убедила всех: более 75 процентов собственного набора тестов Node теперь успешно запускаются под Deno. Это не какая-то поверхностная имитация — речь идет о реальной, проверенной совместимости.
Решающим моментом стало тестирование существующего проекта на Express в среде Deno. Не потребовалось изменять ни одной инструкции импорта. Был добавлен файл deno.json с параметром "nodeModulesDir": "auto", выполнена команда deno install, и время первоначальной установки составило около 900 мс, тогда как при использовании npm в тех же условиях оно составляло более 3 секунд (тестирование проводилось на проекте с React, Vite, Babel и ESLint при чистом кэше). Сервер запустился правильно с первой попытки.
Жалоба 2: система разрешений оказалась неудобной на практике
Концептуально модель разрешений казалась логичной. Однако на практике это означало необходимость каждый раз перепечатывать что-то вроде этого:
deno run --allow-read=./data --allow-write=./data --allow-net=api.example.com --allow-env main.ts
Если забыть установить один флаг, процесс прекращает свою работу. Если добавить новую зависимость, которая читает переменную окружения, процесс снова останавливается. Это больше похоже на настройку правил брандмауэра, чем на создание дополнительного проекта.
В Deno 2.5, выпущенном в сентябре 2025 года, были введены наборы разрешений, определяемые прямо в файле конфигурации. Вы объявляете именованные наборы под ключом "permissions" в файле deno.json, а затем применяете их с помощью флага -P (сокращение от --permission-set):
{
"permissions": {
"default": {
"read": ["./src", "./data"],
"write": ["./data"],
"net": ["api.example.com", "0.0.0.0:3000"],
"env": true
},
"test": {
"read": true,
"write": ["./tmp"],
"net": false
}
}
}
После этого выполнение команды deno run -P main.ts автоматически загружает «стандартный» набор разрешений из файла deno.json, находящегося в рабочей директории, либо можно использовать команду deno test -P=test, чтобы применить отдельный набор разрешений исключительно для тестов. Код тестов и код, предназначенный для производственной эксплуатации, получают разные диапазоны прав без необходимости изменять хотя бы один параметр командной строки. Основные меры безопасности не изменились — просто исчезла сложность, связанная с их использованием.
Также существует переменная окружения DENO_AUDIT_PERMISSIONS, которая генерирует лог в формате JSONL, фиксирующий каждую проверку разрешений во время работы программы. Это позволяет точно узнать, к каким ресурсам пытаются получить доступ ваши зависимости, без необходимости изучения их исходного кода.
Жалоба 3: Мне всё равно понадобилось множество дополнительных инструментов
Преимущество и одновременно недостаток Node заключается в том, что практически каждая задача передаётся отдельному пакету. Нужна форматировка? Используйте prettier. Проверка стиля кода? ESLint и несколько плагинов. Тестирование? Jest или Vitest, плюс файл конфигурации для обработки преобразований TypeScript. Проверка типов? tsc, настроенный отдельно от инструмента, запускающего ваш код. Сборка? Выбирайте из полудюжины инструментов сборки, у каждого из которых свой собственный диалект конфигурации.
Deno объединяет всё это в встроенные подкоманды. Они существовали ещё до выхода версии 2.0:
deno fmt # formats TS, JS, JSON, HTML, CSS, YAML, SQL
deno lint # built-in linter with quick fixes
deno test # test runner with coverage, snapshots, sharding
deno check # type checking
deno compile # single binary, cross-platform, code signing
deno bench # benchmarking
deno doc # documentation generation
Начиная с версии 2.8 появилось ещё шесть подкоманд:
deno audit # security audit of dependencies
deno audit fix # auto-upgrade vulnerable packages to nearest patched version
deno why # explain why a package is installed (traces dependency paths)
deno transpile # strip types, emit .d.ts declarations
deno pack # build an npm-publishable tarball from JSR/Deno code
deno ci # reproducible install for CI (errors without lockfile)
А с версией 2.9 появилось ещё больше:
deno desktop # build native desktop apps via webview (experimental)
deno list # show dependency tree (like npm ls)
deno link/unlink # local package linking for development
Наиболее часто используемый вариант — deno compile. Создав небольшой инструмент командной строки и запустив команду deno compile --target x86_64-unknown-linux-gnu main.ts, получается самодостаточный бинарник. На целевой машине не требуется устанавливать ничего дополнительного — достаточно просто скопировать его на сервер и запустить.
Внимание: полученный бинарник включает V8 и весь средовой стек Deno, поэтому размер типичных приложений составляет примерно 60–100 МБ. Если важен размер, команда
deno compile --bundle(которая по-прежнему нестабильна в версии 2.8) использует агрессивную оптимизацию и может значительно уменьшить размер вывода для простых скриптов. На собственном блоге Deno было показано, что скрипт «hello world» с библиотекой lodash занимает 1,5 МБ при использовании параметров--bundle --minify.
Жалоба 4: миграция реального проекта казалась недостижимой
Самое большое препятствие для изменения среды выполнения обычно не связано с самой средой. Речь идет о файле блокировки, структуре папок node_modules, от которой зависят существующие инструменты, а также о разрозненных вызовах require() в рамках реального кодового базиса.
В Deno 2.9 была введена функция генерации файла блокировки на основе существующих данных. Предположим, проект уже отслеживает свои зависимости с помощью одного из стандартных файлов блокировки менеджеров пакетов — например, package-lock.json от npm, pnpm-lock.yaml, yarn.lock или bun.lock — но никогда не генерировал deno.lock. При выполнении команды deno install в таком проекте создается отсутствующий deno.lock непосредственно на основе найденного файла. Указанные версии совпадают, а хэши целостности также совпадают. Нет необходимости беспокоиться о расхождениях при разрешении зависимостей.
В ситуациях, когда действительно требуется физический каталог node_modules — для нативных дополнений или инструментов, сканирующих файловую систему напрямую, установка "nodeModulesDir": "auto" указывает Deno на необходимость его создания. Также существует опция hoisted-layout ("nodeModulesLinker": "hoisted" в файле deno.json, доступная с версии 2.8) для более старых инструментов, построенных на плоской структуре каталогов npm вместо использования символических ссылок в стиле pnpm.
Механизм shim для Node — это удобная деталь: как только вы устанавливаете сам Deno, он автоматически добавляет заменитель бинарника node в ваш PATH без необходимости вручную настраивать что-либо. Пока ничто другое не предоставляет исполняемый файл node, этот shim перехватывает вызовы командной строки Node, преобразует аргументы и передаёт их Deno. Это означает, что скрипты CI, которые всё ещё вызывают node dist/server.js, продолжают работать без каких-либо изменений. Вы можете отключить это поведение с помощью параметра DENO_DISABLE_NODE_SHIM=1, если не хотите, чтобы это происходило автоматически.
Импорты с прямым указанием модуля — например, запись import fs from „fs“, которая приводит к подключению модуля node:fs — были добавлены в версии 2.0 и стали полностью стабильными; с версии 2.9 они работают без каких-либо флагов или настроек. Нет необходимости возвращаться и переписывать операторы импорта для миграции.
Попытка миграции
Рассмотрим небольшой проект на API Hono (Hono — это легковесная HTTP-фреймворк, сопоставимая с Express), содержащий примерно 15 маршрутов, базу данных Postgres с доступом через ORM Drizzle и процессор фоновых задач. Он работал на Node 22 с использованием pnpm. Все эксперименты проводились с использованием Deno 2.9.3.
Вот как проходила конвертация:
- Выполните команду
deno installиз корневого каталога проекта. Она генерирует файлdeno.lockнепосредственно на основе существующего файлаpnpm-lock.yamlменее чем за две секунды. - Добавьте параметр
"nodeModulesDir": "auto"в только что созданный файлdeno.json. - Запустите приложение с помощью команды
deno run -A src/server.ts.
Всё работает без проблем. Каждый из 15 маршрутов реагирует корректно, запросы на основе Drizzle выполняются как ожидается, а фоновый рабочий процесс продолжает обрабатывать задачи.
Однако сломалось три вещи:
- Один файл тестов использовал функцию
jest.mock(), которой нет в встроенном движке тестов Deno. Замена её на ручной мок занимает менее пяти минут. - Одна из зависимостей использовала переменную
__dirnameв файле формата CommonJS, но Deno загрузил его как модуль ES. Для решения проблемы было добавлено указание"type": "commonjs"в локальные настройки этого конкретного пакета.
process.env.NODE_ENV без явного импорта модуля process. Этот фрагмент на самом деле работал нормально, поскольку с версии 2.0 Deno делает process глобальным объектом — однако флаги разрешений для этого конкретного скрипта не включали параметр env: true, поэтому его потребовалось добавить.Всё преобразование заняло примерно 25 минут от начала до конца. Время запуска с нуля сократилось примерно с 620 мс до 320 мс, согласно измерениям с использованием hyperfine в ходе 50 запусков. Использование памяти в состоянии ожидания (RSS) снизилось с 142 МБ до 64 МБ. Кроме того, можно было удалить четыре отдельных файла конфигурации: prettier, eslint, jest и tsconfig.
Оставшиеся недостатки
В заголовке этой статьи упоминается «всё, что я ненавидел», и вышеупомянутые жалобы действительно являются распространёнными проблемами среди разработчиков Node. Тем не менее есть несколько новых моментов, на которые стоит обратить внимание:
- Покрытие API Node неполное. Уровень прохождения тестов в 75 процентов означает, что четверть тестов всё ещё не проходит. Это может не повлиять на типичную нагрузку, но тем, кто использует нестандартные аспекты
node:vm, программное использованиеnode:inspectorили более сложные функцииnode:cluster, следует заранее проверить панель совместимости на сайте node-test-viewer.deno.dev.
node_modules. Любые пакеты с C++-интеграцией — такие как sharp, bcrypt, sqlite3 и подобные — требуют как настройки nodeModulesDir, так и флага --allow-ffi. Этот подход работает, но представляет собой дополнительный шаг настройки, который легко можно случайно пропустить.postinstall — например, шаги компиляции с помощью node-gyp или команда prisma generate — требуют явного разрешения через параметр --allow-scripts=npm:package-name. Это осознанный шаг в целях безопасности, но он может застать команду врасплох в процессе миграции.process.versions.node и соответственно меняют своё поведение, либо жестко задают пути к файлам вроде /node_modules/.cache. Схема модулей Deno с символическими ссылками в стиле pnpm создаёт проблемы для некоторых из этих инструментов. Существует опция режима hoisted-mode для обхода этой проблемы, но это лишь временное решение, а не по-настоящему эффективное.Ни одна из этих проблем не была настолько серьёзной, чтобы помешать вышеописанной миграции. Однако для других проектов они могут стать препятствием, поэтому стоит проверить их заранее, а не обнаруживать в процессе миграции.
Почему эти исправления остались незамеченными
За 21 месяц было выпущено десять минорных версий, каждая из которых постепенно решала три–пять проблем. Не было ни одной кардинальной переработки, ни какого-либо масштабного события запуска вроде «Deno 3.0». В версии 2.5 были введены детализированные наборы разрешений, в 2.8 достигнута скорость установки на уровне npm, а в 2.9 добавлена функция генерации файлов lockfile; всё это рассматривалось как обычное техническое обслуживание, а не как сенсационные новости.
Если сравнивать это с тем, как обычно освещаются переходы на новые фреймворки: статьи в блогах, доклады на конференциях, руководства по миграции, волна комментариев в социальных сетях, экстремальные мнения и опровержения им. Deno проигнорировал весь этот цикл публичных дискуссий и просто выпустил исправления напрямую.
Разработчики, которые рано отвергли этот среда выполнения, часто делали это потому, что сформировали свое мнение один раз и больше не пересматривали его по мере совершенствования инструментов. Это проявление невнимательности, а не недостаток самого проекта.
Для тех, кто сформировал свое мнение о Deno до версии 2.0, инструмент тех времен практически исчез. То, что существует сегодня, меньше похоже на «интересную среду выполнения TypeScript, которая не может работать с npm», а скорее на «Node без всего накопленного багажа». Раньше были вполне обоснованные претензии, но они уже устранены. Стоит взглянуть на это еще раз.
Связанные статьи
- Замена Jest на встроенный тестовый движок Node в Node 24 — пример реальной миграции показывает, как встроенный тестовый движок Node 24 и поддержка TypeScript ускоряют процесс CI, одновременно устраняя четыре зависимости.