Кратко
СкопированоDeno - это современная среда выполнения JavaScript/TypeScript, созданная Райаном Далом, первоначальным создателем Node.js, в 2018 году. Она была разработана для решения ряда проблем и ограничений, присутствующих в Node.js. Deno внедряет различные функции и архитектурные решения для улучшения безопасности, опыта разработчиков и производительности.
Ключевые отличия от Node.js
СкопированоВ первую очередь Райана смущала безопасность. Node.js предоставляет неограниченные доступы к файловой системе, сети и переменным окружения. Это создаёт значительный риск для безопасности в случае исполнения недоверенного кода. Так же у Node.js нет механизмов для ограничения возможностей скриптов программно. В Deno же код выполняется в песочницах, требуя явных разрешений на доступы к файловой системе, сети и переменным окружения. Управлять доступами необходимо из командной строки. Так, например, для того, чтобы разрешить коду использовать сеть, мы должны запустить скрипт из терминала с флагами:
// my-script.jsconst url = "https://api.github.com/users/denoland";async function fetchData() { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Request error! status: ${response.status}`); } const data = await response.json(); console.log('Data': data); } catch (error) { console.error("Error fetching data:", error); }}fetchData();
// my-script.js const url = "https://api.github.com/users/denoland"; async function fetchData() { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Request error! status: ${response.status}`); } const data = await response.json(); console.log('Data': data); } catch (error) { console.error("Error fetching data:", error); } } fetchData();
deno run --allow-net my-script.js
deno run --allow-net my-script.js
А вот пример запуска скрипта с параметрами доступа к файловой системе:
// my-other-script.jsconst { readTextFile, writeTextFile } = Deno;async function modifyFileContent() { try { const inputFile = 'input.txt'; // путь до файла может быть абсолютным и относительным const inputContent = await readTextFile(inputFile); console.log(`Read from ${inputFile}: ${inputContent}`); // Приведём строки к верхнему регистру: const modifiedContent = inputContent.toUpperCase(); const outputFile = 'output.txt'; await writeTextFile(outputFile, modifiedContent); console.log(`Written to ${outputFile}: ${modifiedContent}`); } catch (error) { console.error('Error with file operations:', error); }}modifyFileContent();
// my-other-script.js const { readTextFile, writeTextFile } = Deno; async function modifyFileContent() { try { const inputFile = 'input.txt'; // путь до файла может быть абсолютным и относительным const inputContent = await readTextFile(inputFile); console.log(`Read from ${inputFile}: ${inputContent}`); // Приведём строки к верхнему регистру: const modifiedContent = inputContent.toUpperCase(); const outputFile = 'output.txt'; await writeTextFile(outputFile, modifiedContent); console.log(`Written to ${outputFile}: ${modifiedContent}`); } catch (error) { console.error('Error with file operations:', error); } } modifyFileContent();
deno run --allow-read --allow-write my-other-script.js
deno run --allow-read --allow-write my-other-script.js
Если мы установим пакет, которому необходимы дополнительные права, то при попытке выполнения увидим, что для выполнения скрипта Deno необходимо разрешение на запись:
deno run npm:file-access-package ┌ Deno requests write access to /usr/bin/. ├ Requested by `file-access-package` ├ Run again with --allow-write to bypass this prompt. └ Allow? [y/n] (y = yes, allow; n = no, deny)
deno run npm:file-access-package ┌ Deno requests write access to /usr/bin/. ├ Requested by `file-access-package` ├ Run again with --allow-write to bypass this prompt. └ Allow? [y/n] (y = yes, allow; n = no, deny)
Вторая проблема - управление зависимостями. В Node.js библиотеки и фреймворки устанавливаются с помощью NPM (Node Package Manager). Это означает, что каждый раз, когда вы добавляете новую библиотеку, она может иметь свои собственные зависимости. Например, если вы установите библиотеку "A", она может зависеть от библиотек "B" и "C", а те, в свою очередь, могут иметь свои зависимости. Это создает очень сложную структуру, похожую на дерево, где одна библиотека зависит от другой и так далее. Это усложняет управление зависимостями, потому что нужно следить за множеством версий разных библиотек. Если одна библиотека требует одну версию зависимости, а другая - другую, может возникнуть конфликт версий, и программа может перестать работать. Ещё централизация реестра зависимостей немного осложняет мененджмент пакетов из разных реестров: необходимо указать список реестров в .npmrc, авторизоваться или прописать явно токены авторизации и так далее. Кроме того, сообщество ещё не забыло инцидент с left-pad.
Deno вместо этого предлагает механизм импорта по URL с локальным кешированием, что приводит к созданию плоской структуры зависимостей. Каждый URL рассматривается как отдельный, уникальный ESM модуль, что убирает возможность возникновения конфликтов версий начисто, а так же для доступов в различные реестры можно обойтись наличием VPN, т.к. в реестре теперь лежит статический JS или TS файл, а не .tar.gz архив. Однако децентрализация всё ещё оставляет возможности повторения ситуации с left-pad, особенно при использовании открытых, а не приватных репозиториев. Также в релизе 1.28 была добавлена возможность использования npm пакетов из registry.npmjs.org.
Как это выглядит на практике:
// импорт из самой последней, самой свежей версии пакета std:import { assertEquals } from "https://deno.land/std/testing/asserts.ts";// импорт из конкретной версии пакета, в данном случае 0.104.0:import { serve } from "https://deno.land/std@0.104.0/http/server.ts";// импорт из приватного реестра с моего сервера:import { SimpleButton } from "https://nexus.vzhyx.digital/deno-test/chamomile-ui@1.1.2/index.ts";// импорт из NPM:import React from "npm:react@18.2.0"
// импорт из самой последней, самой свежей версии пакета std: import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; // импорт из конкретной версии пакета, в данном случае 0.104.0: import { serve } from "https://deno.land/std@0.104.0/http/server.ts"; // импорт из приватного реестра с моего сервера: import { SimpleButton } from "https://nexus.vzhyx.digital/deno-test/chamomile-ui@1.1.2/index.ts"; // импорт из NPM: import React from "npm:react@18.2.0"
Следом контрибьюторы посчитали необходимым привести работу с модулями к стандарту. Node.js, конечно, поддерживает ESModules, однако для этого необходимо указать "type": "module" в package.json или использовать файлы с расширением .mjs, оставляя работу с CommonJS с помощью require() поведением по умолчанию. Получается, код в Deno и в браузере выглядит консистентнее, чем в Node.js.
Таким же образом Райан поступил и с асинхронными операциями. Node.js поддерживает промисы и async/await, однако значительная часть кор-фичей построена на колбэках, или получила асинхронный аналог, как, например, fs.readFile, который принимает в себя путь до файла и колбэк для обработки прочитанных данных, возвращая undefined, и fs.readFileSync, который принимает в себя путь до файла и возвращает его содержимое. Deno кор-фичи сразу спроектированы асинхронно, и разработчику теперь не нужно запоминать вариации решения одной и той же задачи, что также упрощает изучение апи.
Ещё одно крупное нововведение - встроенный тулинг для работы с качеством кода. В Node.js это отдано на откуп отдельным пакетам: ESLint, Prettier, Mocha, Jest и так далее, а Deno поставляет это всё "из коробки". Запустив утилиту lint командой deno lint мы получим список отклонений от кодстайла, а запустив утилиту fmt командой deno fmt мы получим отформатированный код. Бонусом deno избавляет нас от пачки конфигурационных файлов для линтеров, форматтеров и прочих утилит, всё настраивается в одном месте - deno.json:
{ "imports": { "std/": "https://deno.land/std@0.224.0/" }, "tasks": { "dev": "deno run --watch main.ts" }, "lint": { "include": ["src/"], "exclude": ["src/testdata/", "src/fixtures/**/*.ts"], "rules": { "tags": ["recommended"], "include": ["ban-untagged-todo"], "exclude": ["no-unused-vars"] } }, "fmt": { "useTabs": true, "lineWidth": 80, "indentWidth": 4, "semiColons": true, "singleQuote": true, "proseWrap": "preserve", "include": ["src/"], "exclude": ["src/testdata/", "src/fixtures/**/*.ts"] }}
{ "imports": { "std/": "https://deno.land/std@0.224.0/" }, "tasks": { "dev": "deno run --watch main.ts" }, "lint": { "include": ["src/"], "exclude": ["src/testdata/", "src/fixtures/**/*.ts"], "rules": { "tags": ["recommended"], "include": ["ban-untagged-todo"], "exclude": ["no-unused-vars"] } }, "fmt": { "useTabs": true, "lineWidth": 80, "indentWidth": 4, "semiColons": true, "singleQuote": true, "proseWrap": "preserve", "include": ["src/"], "exclude": ["src/testdata/", "src/fixtures/**/*.ts"] } }
И, конечно, в отличие от Node.js, который поддерживает TypeScript только через установку и конфигурацию отдельных пакетов, Deno из коробки предоставляет компилятор ts, что позволяет нам писать на TypeScript без установки дополнительных пакетов.
Последнее значительное отличие Deno от Node.js - исходный код. Node.js написан преимущественно на C|C++, а Deno - на Rust, на языке, известном более надёжной работой с памятью и мультипоточностью, а также даёт возможность расширять функциональность Deno с помощью плагинов, написанных на Rust.
Использование
СкопированоDeno поддерживают и используют достаточно много компаний: Slack, Netlify, SalesForce, Tencent, однако нет подробной информации о проектах и статистики использования.
Минусы
СкопированоНесмотря на то, что Deno позиционируется как следующий этап эволюции Node.js, как переосмысление и исправление ошибок, Deno также не лишён и минусов:
- Экосистема. Node.js развивается с 2009 года, и имеет более двух миллионов пакетов, в отличие от Deno. Также хоть Deno и внедрил поддержку npm пакетов, совместимость не полная и перенос существующего проекта с Node.js на Deno может затребовать колоссального рефакторинга.
- Поддержка в IDE. Несмотря на то, что у Deno имеются все средства обеспечения качества кода "из коробки", IDE и редакторы могут не поддерживать плагины или лишиться поддержки при обновлении версии Deno, и проблемы уже возникали.
- TS в коробке. Поддержка TS напрямую кажется отличным решением, однако даёт дополнительную нагрузку при компиляции "на лету". Это может приводить к замедлению работы и увеличению потребления ресурсов сервера.
- Стабильность, поддержка. Так как Deno всё ещё является новым рантаймом, развивается, меняет, добавляет и выводит API, он не может обеспечить такую же стабильность, как у Node.js. Кроме того, политика долгосрочной поддержки (LTS) всё ещё в планах.