Deno: время Node.JS уходит?
Прошло примерно 18 месяцев с внутреннего релиза Deno, вышел preview release, появилось несколько статей на Хабре, и Райан ездит по конференциям и рассказывает о нём. Однако я нигде так и не видел сколько-нибудь вдумчивого разбора этого проекта — почему-то все ограничиваются переводом документации…
Что же, давайте попробуем это сделать сейчас. Последние 5 лет я пишу на Node.JS, а компания OneTwoTrip, где я сейчас работаю, пишет проекты на ноде около 9 лет (да, это я писал историю про 9 лет в монолите на ноде). Так что анализ должен выйти неплохой. Тем более что я его уже рассказал на Moscow Node.JS Meetup 10, и было интересно. Кстати, если вам удобнее слушать, а не читать, то послушать и посмотреть можно вот тут. Моё выступление второе, я чувак в розовой рубашке.
Как ни странно, для того, чтобы понять, откуда и зачем возник проект, надо провалиться в прошлое. Так что вкидываем немного плутония, поднимаем двери нашего делореана, и пускаемся в путешествие — посмотрим на важные 10 лет, которые сделали Node.JS такой, как мы видим сейчас.
Вперёд в прошлое
2009
Райан Дал анонсирует Node.JS, вот она — самая первая презентация на JSConf 2009.
2010
Появляются express, socket.io — основные на текущий момент кирпичики почти любого сервиса.
Появляются сумасшедшие люди, которые реально пишут на этом серверный код!
2011
С Node.JS начинают заигрывать крупные ребята — в том числе Uber и Linkedin.
Релиз npm 1.0.
Node начинает работать на Windows.
2012
Райан уходит от разработки Node.JS. Запомните. Это был 2012 год. Так что Райан безусловно является создателем, и многие сделал для экосистемы —, но последующие 7 лет прошли без его участия.
2013
Node в Paypal, Walmart, eBay.
Появляется Koa — помните, сколько копий было сломано о генераторы?
2014
Node в Netflix. Начинаются попытки оформить проект в что-то более взрослое, с открытой моделью управления консультативным советом. Наблюдается техническая стагнация, приведшая к появлению форка io.js.
2015
Работа над ошибками. Слияние io.js и Node в экстазе под эгидой Node Foundation и выход Node 4. Надо сказать, именно эту версию я считаю первой, на которой реально можно было что-то разрабатывать. Если кто писал на версиях 0.xx — то вы помните про var, callback hell, кучу разных библиотек для упрощения асинхронной работы (вроде step и async. Да, async — это не только async await, но ещё и npm библиотека).
2016
Инцидент с leftpad, который до сих пор злые языки припоминают экосистеме. Сколько же было статей и нападок. Ну, haters gonna hate. Однако, важные уроки из этого были вынесены.
2017
Прорывной год. Я не буду упоминать все релизы ноды и рост количества установок модулей с npm, однако именно в этом году количество сервисов на Node.JS превысило 8 миллионов, а количество установок — 3 биллиона в неделю. Абсолютно космические цифры, которые сложно даже представить.
Так же появился N-API, и Node.JS был снова форкнут в Ayo.js. Очень смешная и поучительная история про SJW — она стоит отдельной статьи, поэтому не буду на ней останавливаться — просто рекомендую прочитать на досуге. Только проспойлерю, что форк благополучно умер.
2018
Вторая массовая истерия со времени leftpad — теперь про то как event-stream ворует биткоины. Сотни постов про небезопасность экосистемы. Посты-фантазии про то как npm пакеты воруют данные кредитных карт. Комьюнити поливают грязью как из шланга. Надо сказать, это было очень полезно, и выводы так же были сделаны — о них чуть позже.
Так же Райан внезапно взрывает комьюнити постами про то, что серьёзные сервисы стоит писать на Go, описывает 10 вещей в Node, о которых он сожалеет, и анонсирует Deno, который решает все проблемы.
2019
Deno выходит в preview release, появляется куча статей на хабре, и вот вы сейчас читаете одну из них.
Назад в настоящее
Надеюсь, после этой экскурсии стало более понятно, какие были больные места у экосистемы, и как она развивалась — имея данный контекст, гораздо проще понимать, что происходит сейчас.
10 вещей в Node.JS, о которых сожалеет Райан Дал
К сожалению, я не нашёл навскидку статью с переводом доклада, так что перечислю их здесь вкратце, и здесь же откомментирую.
- Отсутствие поддержки промисов в начале пути. Да, всё было бы проще, если бы Райан не выпилил промисы, сочтя их за лишнее усложнение, которое не взлетало в начале разработки ноды. Потерянного времени на весь этот callback hell конечно жалко —, но в 2019 году у всех вменяемых библиотек промисы являются основным интерфейсом. Более того, даже системные библиотеки наконец предоставляют промисы.
- Безопасность системных вызовов и обращений по сети. С одной стороны — да, хорошо когда всё безопасно. С другойт стороны — непонятно, чем в этом плане нода оказалась хуже любой другой среды…
- Сборка нативных модулей при помощи GYP. Да, наверное, это было лишним, но кто мог знать, что хром с неё уйдёт. Опять же — если хром ушёл, значит, сможем уйти и мы…
- Излишество package.json. NPM как монопольное регистри. Аргумент про package.json немного странный. Например, Райан говорит, что там есть всякий мусор вроде лицензии. Но если бы её там не было — как вы могли бы быстро узнать лицензии модулей, используемых в вашем проекте?… Аргумент насчёт NPM больше похож на правду, но остановимся на этом подробнее позже.
- Node modules. Сложное разрешение зависимостей, работает не так как в браузере. Да, всё так. Стабильно зависимости начали ставиться без всяких чудес только на 4–5 версии npm. Но механизм работает, и позволяет делать удивительные вещи — на текущий момент это прекрасно. Что же касается совместимостью с браузером — что бы вы ни делали, всё равно будут этапы обработки кода вроде транспиляции и сбора бандла. Так что вряд ли node modules имеет какое-то значение в данном контексте.
- Require без расширения и его непопределённость. Да, наверное плохо. Но не настолько, чтобы об этом упоминать…
- index.js как лишнее усложнение. Тоже слишком тривиальный и скучный пункт, чтобы его описывать.
Кстати, заметьте, я говорил, что Райан сожалеет о 10 вещах, а пунктов всего 7. Это не ошибка, я несколько раз пересматривал его доклад, и обзоры доклада. То ли это была сложная шутка на тему обработки числовых значений, то ли Райан просто постеснялся назвать ещё 3 пункта…
Но ладно, поняли, какие проблемы, поехали дальше. Логично, что в Deno Райан решил избавиться от всех проблем Node.JS. Посмотрим, что у него вышло.
Из чего состоит Deno
- Deno написан на Rust.
- В качестве Event loop в Deno используется Tokio, написанный опять же на Rust.
- Deno поддерживает Typescript «из коробки».
- Ну, а код исполняется при помощи того же V8, который захватил весь мир.
На первый взгляд звучит неплохо, однако рассмотрим эти пункты ближе.
Rust. Не, прошу понять меня правильно — я считаю, что Rust и Go это прекрасные языки, и я искренне рад, что они есть. Они дают возможность писать низкоуровневый код быстрее и безопаснее, чем на С++. Однако, есть нюанс — в лучшем случае он будет не медленнее реализации на С++. Так что смысла писать полный аналог системных обвязок и ивент лупа лично я не вижу — вряд ли это может принести какую-то реальную пользу, поскольку это те вещи, которые в какой-то момент просто доходят до оптимального состояния, и дальше фактически не оптимизируются.
TypeScript. Опять же — я ничего не имею против TS. Его использует огромное количество компаний и разработчиков, и он уже показал свою состоятельность. Но Deno всего лишь прячет транспилятор в бинарник, а транспилированный код — в особые директории. То есть под капотом происходит всё то же самое, и выгоды в этом нет, за исключением эстетической. Зато есть минус — версия транспилятора оказывается намертво прибита к версии Deno. Не уверен, что это хорошо — легко представить себе ситуацию, когда вам хочется обновить либо транспилятор либо рантайм. Но не и то и другое сразу.
Так что пока ничего вкусного не видно. Пойдём дальше, заглянем в основные фичи.
Основные отличия Deno и Node.JS
Deno не использует npm. Нет централизованного реестра. Модули импортируются по URL. Нет package.json.
То есть, код выглядит примерно так:
import { test, runIfMain } from "https://deno.land/std/testing/mod.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
test(function t1() {
assertEquals("hello", "hello");
});
При первом запуске код загружается и кешируется, а потом используется кеш. Версионность поддерживается при помощи указания версии в урлах. Не знаю, почему это вызывает у всех столько восторга. Я вижу всего два варианта развития событий:
- Всё пойдёт так как того хочет Райан. Разработчики будут заливать код компонентов на личные веб сайты, создавать папки с версиями, и все будут качать пакеты оттуда. Гмм. Мне кажется, это решительный скачок в прошлое:
1.1. Любой код, в том числе с указанной версией, может бесконтрольно меняться на сервере автора.
1.2. Не будет никакого источника, из которого можно узнать о стабильности пакета, о количестве его переиспользований, о наличии в нём закладок и проблем.
1.3. Автору пакета придётся самому заботиться о том, чтобы его сервер держал нагрузку от скачивающих пакеты разработчиков. Неужели мы этого не наелись около 2012 года, когда npm чаще лежал, чем работал? Как говорят наклейки на автомобилях, «можем повторить»? - Другой вариант, более реальный. Разработчики будут класть пакеты на github, gitlab, или любой другой репозиторий, который держит нагрузку и прозрачен для комьюнити. Вроде всё хорошо. Остаётся один вопрос. От npm мы, предположим, откажемся —, но какая разница, какой именно будет централизованный репозиторий? Будет всё ровно то же самое — просто вид сбоку. Даже проект децентрализованных репозиториев Entropic, который, кажется, тихо скончался — и то выглядел интереснее.
Отдельного рассмотрения стоит вопрос воспроизводимых билдов. Поскольку никто не гарантирует, что вы скачаете с какого-то левого сервера одно и то же (в особенности если не указали в урле версию) — то Райан предлагает… Хранить исходный код импортируемых пакетов в репозитории проекта… На дворе точно 2019? Может, Райан не в курсе, что с 2012 года в Node.JS появился сначала shrinkwrap, а потом lock file? Которые решают эту проблему гораздо проще, нагляднее и экономичнее?
На мой взгляд, загрузка модулей по URL — очень странное и спорное решение, основное достоинство которого — вау эффект для джуниор разработчиков.
Все асинхронные операции в Deno возвращают Promise.
В целом круто, да. Но с 2012 года утекло реально много воды — и в ноде все вменяемые библиотеки давно работают на промисах. И даже системные библиотеки на них почти переползли. Поэтому это сложно считать каким-то конкурентным преимуществом.
Deno всегда завершает работу на Uncaught Errors
Странно видеть это в списке значимых отличий. В Node.JS вы можете делать ровно то же самое. Если вам это нужно.
Используются ES Modules, а не require
Да, хорошо, что бэк и фронт переходят на один формат. Но знаете, в Node.JS сейчас тоже есть поддержка ES Modules…
Deno требует разрешения на работу с сетью, файлами и переменными окружения.
Звучит классно! Но реализация… Реализация — это просто набор флагов allow-read, allow-net, allow-run, allow-env. Выглядит это как-то так:
deno --allow-read=/etc https://deno.land/std/examples/cat.ts /etc/passwd
Это опять же вызывает у меня вопросы:
- В большом приложении скрипт запуска превратится в помойку флагов.
- Скорее всего, эти ограничения просто превратятся в практику запуска с флагом --allow-all.
- Практически ни в какой другой среди нет подобных ограничений. И все отлично с этим живут. По той простой причине, что уже много лет как правами на доступ к файлам можно управлять на уровне разрешений пользователя, от которого запущен процесс. А вопрос сетевого взаимодействия отлично решается файрволами. Почему Райан решил, что это вопрос рантайма — мне глубоко непонятно.
- Ну и последнее. Контейнеры не только появились, они прочно вошли в употребление, и даже перестали быть хайповой темой. И они отлично решают эти вопросы. Появляется ощущение, что Райан вошёл в 2019 год на машине времени прямо из 2012, и только это всё объясняет — тогда до релиза докера был ещё целый год…
Наше время. Наши дни. NPM.
Ну и в целом мне хотелось напомнить, что произошло с npm с 2012 года:
- Пакеты не исчезают. Удаление и изменение загруженной версии запрещено.
- Lock file обеспечивает воспроизводимые сборки.
- Есть аудит безопасности. Причём с помощью github, snyk и самого npm.
- Есть статистика использования и зависимостей.
- Есть альтернативные клиенты.
- Есть возможность ставить пакеты из иных источников — гит, гитхаб, что угодно.
- Есть прокси регистри.
А главным достоинством npm я считаю… То, что его в любой момент можно выкинуть из экосистемы. Есть протокол, есть клиенты, есть другие регистри… Как только «Акела промахнётся», любая большая компания может поднять альтернативный регистри — facebook, google, microsoft, gitlab… Пока что этого не случалось ровно по той причине, что npm работает достаточно стабильно и отвечает потребностям сообщества.
Подводя итоги
Пройдём по пунктам:
- Rust — не преимущество.
- TypeScript — не преимущество.
- Загрузка модулей по URL без NPM — скорее два шага назад.
- Улучшения безопасности — выглядит ужасно.
- Остальные отличия непринципиальны. Разве что лого. Лого офигенное. Люблю динозавров!
В итоге я просто не вижу смысла в использовании Deno. Терпеть не могу позицию «не пробовал, но осуждаю» — но, пока что даже Райан говорит, что Deno ещё сырой — поэтому пробовать я его не стал.
Однако я очень хотел найти антагониста, который бы сказал мне, что я не прав, что Райан сделал крутую штуку, и что я не понял её применения. Я много обсуждал Deno с коллегами, друзьями, и рассказывал всё это на Moscow Node.JS Meetup — и никто не высказал мне альтернативного мнения. Отчасти поэтому я пишу статью на хабр — скажите, может я всё же что-то не понял или не заметил?