Deno - время Node.JS уходит?

Оригинал поста на хабре

Прошло примерно 18 месяцев с внутреннего релиза Deno, вышел preview release, появилось несколько статей на Хабре, и Райан ездит по конференциям и рассказывает о нём. Однако я нигде так и не видел сколько-нибудь вдумчивого разбора этого проекта - почему-то все ограничиваются переводом документации…

Что же, давайте попробуем это сделать сейчас. Последние 5 лет я пишу на Node.JS, а компания OneTwoTrip, где я сейчас работаю, пишет проекты на ноде около 9 лет (да, это я писал историю про 9 лет в монолите на ноде). Так что анализ должен выйти неплохой. Тем более что я его уже рассказал на Moscow Node.JS Meetup 10, и было интересно. Кстати, если вам удобнее слушать, а не читать, то послушать и посмотреть можно вот тут. Моё выступление второе, я чувак в розовой рубашке.

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

Вперёд в прошлое

2009

Райан Дал анонсирует Node.JS, <a href=”https://www.youtube.com/watch?v=ztspvPYybIY">вот она</a> - самая первая презентация на 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, о которых сожалеет Райан Дал

К сожалению, я не нашёл навскидку статью с переводом доклада, так что перечислю их здесь вкратце, и здесь же откомментирую.

  1. Отсутствие поддержки промисов в начале пути. Да, всё было бы проще, если бы Райан не выпилил промисы, сочтя их за лишнее усложнение, которое не взлетало в начале разработки ноды. Потерянного времени на весь этот callback hell конечно жалко - но в 2019 году у всех вменяемых библиотек промисы являются основным интерфейсом. Более того, даже системные библиотеки наконец предоставляют промисы.
  2. Безопасность системных вызовов и обращений по сети. С одной стороны - да, хорошо когда всё безопасно. С другой стороны - непонятно, чем в этом плане нода оказалась хуже любой другой среды….
  3. Сборка нативных модулей при помощи GYP. Да, наверное, это было лишним, но кто мог знать, что хром с неё уйдёт. Опять же - если хром ушёл, значит, сможем уйти и мы….
  4. Излишество package.json. NPM как монопольное регистри. Аргумент про package.json немного странный. Например, Райан говорит, что там есть всякий мусор вроде лицензии. Но если бы её там не было - как вы могли бы быстро узнать лицензии модулей, используемых в вашем проекте?.. Аргумент насчёт NPM больше похож на правду, но остановимся на этом подробнее позже.
  5. Node modules. Сложное разрешение зависимостей, работает не так как в браузере. Да, всё так. Стабильно зависимости начали ставиться без всяких чудес только на 4-5 версии npm. Но механизм работает, и позволяет делать удивительные вещи - на текущий момент это прекрасно. Что же касается совместимостью с браузером - что бы вы ни делали, всё равно будут этапы обработки кода вроде транспиляции и сбора бандла. Так что вряд ли node modules имеет какое-то значение в данном контексте.
  6. Require без расширения и его неопределённость. Да, наверное, плохо. Но не настолько, чтобы об этом упоминать…
  7. index.js как лишнее усложнение. Тоже слишком тривиальный и скучный пункт, чтобы его описывать.

Кстати, заметьте, я говорил, что Райан сожалеет о 10 вещах, а пунктов всего 7. Это не ошибка, я несколько раз пересматривал его доклад, и обзоры доклада. То ли это была сложная шутка на тему обработки числовых значений, то ли Райан просто постеснялся назвать ещё 3 пункта…

Но ладно, поняли проблемы, поехали дальше. Логично, что в Deno Райан решил избавиться от всех проблем Node.JS. Посмотрим, что у него вышло.

Из чего состоит Deno

  1. Deno написан на Rust.
  2. В качестве Event loop в Deno используется Tokio, написанный опять же на Rust.
  3. Deno поддерживает Typescript “из коробки”.
  4. Ну а код исполняется при помощи того же V8, который захватил весь мир.

На первый взгляд звучит неплохо, однако рассмотрим эти пункты ближе.

Rust. Не, прошу понять меня правильно - я считаю, что Rust и Go это прекрасные языки, и я искренне рад, что они есть. Они дают возможность писать низкоуровневый код быстрее и безопаснее, чем на С++. Однако, есть нюанс - в лучшем случае он будет не медленнее реализации на С++. Так что смысла писать полный аналог системных обвязок и ивент лупа лично я не вижу - вряд ли это может принести какую-то реальную пользу, поскольку это те вещи, которые в какой-то момент просто доходят до оптимального состояния, и дальше фактически не оптимизируются.

TypeScript. Опять же - я ничего не имею против TS. Его использует огромное количество компаний и разработчиков, и он уже показал свою состоятельность. Но Deno всего лишь прячет транспилятор в бинарник, а транспилированный код - в особые директории. То есть под капотом происходит всё то же самое, и выгоды в этом нет, за исключением эстетической. Зато есть минус - версия транспилятора оказывается намертво прибита к версии Deno. Не уверен, что это хорошо - легко представить себе ситуацию, когда вам хочется обновить либо транспилятор либо рантайм. Но не и то и другое сразу.

Так что пока ничего вкусного не видно. Пойдём дальше, заглянем в основные фичи.

Основные отличия Deno и Node.JS

Deno не использует npm. Нет централизованного реестра. Модули импортируются по URL. Нет package.json.

То есть, код выглядит примерно так:

1
2
3
4
5
6
7
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. Не будет никакого источника, из которого можно узнать о стабильности пакета, о количестве его переиспользований, о наличии в нём закладок и проблем.
    1. Автору пакета придётся самому заботиться о том, чтобы его сервер держал нагрузку от скачивающих пакеты разработчиков. Неужели мы этого не наелись около 2012 года, когда npm чаще лежал, чем работал? Как говорят наклейки на автомобилях, “можем повторить”?
  2. Другой вариант, более реальный. Разработчики будут класть пакеты на 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. Выглядит это как-то так:

1
deno --allow-read=/etc https://deno.land/std/examples/cat.ts /etc/passwd

Это опять же вызывает у меня вопросы:

  1. В большом приложении скрипт запуска превратится в помойку флагов.
  2. Скорее всего, эти ограничения просто превратятся в практику запуска с флагом –allow-all.
  3. Практически ни в какой другой среде нет подобных ограничений. И все отлично с этим живут. По той простой причине, что уже много лет как правами на доступ к файлам можно управлять на уровне разрешений пользователя, от которого запущен процесс. А вопрос сетевого взаимодействия отлично решается файрволами. Почему Райан решил, что это вопрос рантайма - мне глубоко непонятно.
  4. Ну и последнее. Контейнеры не только появились, они прочно вошли в употребление, и даже перестали быть хайповой темой. И они отлично решают эти вопросы. Появляется ощущение, что Райан вошёл в 2019 год на машине времени прямо из 2012, и только это всё объясняет - тогда до релиза докера был ещё целый год…

Наше время. Наши дни. NPM.

Ну и в целом мне хотелось напомнить, что произошло с npm с 2012 года:

  1. Пакеты не исчезают. Удаление и изменение загруженной версии запрещено.
  2. Lock file обеспечивает воспроизводимые сборки.
  3. Есть аудит безопасности. Причём с помощью github, snyk и самого npm.
  4. Есть статистика использования и зависимостей.
  5. Есть альтернативные клиенты.
  6. Есть возможность ставить пакеты из иных источников - гит, гитхаб, что угодно.
  7. Есть прокси регистри.

А главным достоинством npm я считаю… То, что его в любой момент можно выкинуть из экосистемы. Есть протокол, есть клиенты, есть другие регистри… Как только “Акела промахнётся”, любая большая компания может поднять альтернативный регистри - facebook, google, microsoft, gitlab… Пока что этого не случалось ровно по той причине, что npm работает достаточно стабильно и отвечает потребностям сообщества.

Подводя итоги

Пройдём по пунктам:

  1. Rust - не преимущество.
  2. TypeScript - не преимущество.
  3. Загрузка модулей по URL без NPM - скорее два шага назад.
  4. Улучшения безопасности - выглядит ужасно.
  5. Остальные отличия непринципиальны. Разве что лого. Лого офигенное. Люблю динозавров!

В итоге я просто не вижу смысла в использовании Deno. Терпеть не могу позицию “не пробовал, но осуждаю” - но, пока что даже Райан говорит, что Deno ещё сырой - поэтому пробовать я его не стал.

Однако я очень хотел найти антагониста, который бы сказал мне, что я не прав, что Райан сделал крутую штуку, и что я не понял её применения. Я много обсуждал Deno с коллегами, друзьями, и рассказывал всё это на Moscow Node.JS Meetup - и никто не высказал мне альтернативного мнения. Отчасти поэтому я пишу статью на хабр - скажите, может я всё же что-то не понял или не заметил?