Pull to refresh

Deno v1.0: Безопасная среда выполнения для JavaScript и TypeScript. Обзор возможностей

Reading time 14 min
Views 9.3K


  1. Вступление
  2. Установка
  3. Как это выглядит внутри
  4. Функциональность
  5. WASM, RUST, Плагины
  6. Debugging, IDE
  7. Тестирование
  8. Compiler API
  9. CI
  10. Разное

Вступление


Если вы уже оказались за чтением этой статьи, то наверняка уже слышали про выступление Ryan Dahl, создателя NodeJS, на JSConf, где он выступил с докладом и рассказал о ключевых ошибках, которые были сделаны при проектировании NodeJS. В этом же докладе он обьявил о новом проекте: Deno, в котором будут учтены ошибки предыдущего проектирования.


Вдохновившись этим анонсом, я внимательно стал следить за проектом и пробовать реализовывать на нем различные проекты.


И вот уже спустя 2 года, перенося несколько раз, выпустили релиз.


В этой статье я постараюсь дать больше, чем стандартная документация Deno. К сожалению, она пока не такая идеальная, чтобы в нем был описан весь функционал. А также постараюсь озвучить некоторые негласные стандарты разработки под Deno, и с чем приходиться сталкиваться авторам различных библиотек. Дополнительно, постараюсь привлечь вас и опробовать Deno самому.


Установка


Думаю, что начнем с установки, и разберем что, куда, как и почему устанавливается.


Если же вы перейдете на сайт deno.land, то сразу увидите инструкцию для установки.


Using Shell:


curl -fsSL https://deno.land/x/install/install.sh | sh

Or using PowerShell:


iwr https://deno.land/x/install/install.ps1 -useb | iex

На сайте также имеются дополнительные варианты для установки через различные пакетные менеджеры, типа scoop, homebrew. Здесь я должен предупредить, что установка с помощью пакетных менеджеров несет в себе опасность в виде несформировавшегося до конца полного механизма удаления и обновления. Лучше всего контролировать версии и пути куда будет установлено. По этому URL https://github.com/denoland/deno_install вы сможете найти ответы на частые вопросы по установке.


Также, для обновления до текущей версии, предусмотрена команда deno upgrade


Ну а теперь хочу разобрать сам установщик на примере установки в Windows. Хочу заметить, что к моменту релиза был устранен ряд проблем в различии установок между платформами, и теперь не стоит вопрос в предпочтении платформы для запуска Deno.


Установка в Windows, что происходит


Почти все, что есть в Deno, опирается на github, и установка будет производится именно с него, и последний релиз будет взят именно с него.


В дальнейшем, для обеспечения работы deno в целом, по пути {username}\AppData\Local\deno
создадутся следующие файлы и директории:


  • deps,

В этой директории будут расположены все загруженные когда либо зависимости, сгруппированные по пути скачивания, а так же рядом с каждым файлом будет лежать свой файл метадаты, в котором указаны response headers, от сервера к которому было обращение во время билда и, конечно же, сам url. Это, собственно, является хранилищем ваших зависимостей, куда в первую очередь во время запуска вашего проекта обратится Deno. Их также можно будет перезагрузить, если понадобится, с помощью добавления флага -r в команду deno run. Для тех, кто беспокоится о lock файлах и кросс зависимостях, об этом будет подробно описано в разделе “Функционал Deno”


  • gen

Здесь будут располагаться ваши скомпилированные в js, typescript файлы. Это, как вы наверно уже понимаете, обеспечивает быстрый старт приложения после первого билда.
Скомпилированные проекты тут и будут находиться. Например, по пути gen/file/C/dev/my_first_project


  • deno_history.txt

В этом файле ведется краткосрочная история ввода команд в deno в консольном режиме.


  • lib.deno.d.ts, lib.deno_runtime.d.ts

В этом файле расположены тайпинги самого deno core. Знания о них вам понадобятся, когда попытаетесь играть с разными версиями Deno.


Как это выглядит внутри


Внешний интерфейс Typescript. Здесь реализованы общедоступные интерфейсы, API и большинство важных функциональных возможностей, которые напрямую не требуют системных вызовов. Сгенерированный автоматический документ API доступен в данный момент по адресу https://deno.land/typedoc. Существует документация специально для std и x пакетов: https://doc.deno.land/. Про пакеты std, x и прочих будет описано в разделе “Функционал Deno”.


Слой Typescript/ Javascript рассматривается как непривилегированный, который не имеет доступа к файловой системе или сети (поскольку они работают в V8, которая является “песочницей”). Это возможно только благодаря передаче сообщений в бэкенд Rust, который является “привилегированным”. Поэтому многие API-интерфейсы Deno (особенно вызовы файловой системы) реализованы на стороне Typescript как чисто создающие буферы для данных, отправляющие их в бэкенд Rust через промежуточный слой Rusty V8 и ожидающие (синхронно и асинхронно) отправки результата назад. Функции Deno.core.send(), Deno.core.recv() именно этим и занимаются.


Rusty V8 — это тонкий слой между интерфейсом Typescript и бэкэндом Rust, служащий для взаимодействия с V8 и предоставления только необходимых привязок. Он также используется для запуска платформ V8 и создания/загрузки снимка V8. Подробнее о снимках V8 можно посмотреть блоге V8 https://v8.dev/blog/custom-startup-snapshots. Стоит отметить, что снимок для Deno также содержит компилятор Typescript. Это позволяет значительно сократить время запуска компилятора.


Rust Backend. В настоящее время серверная часть, или «привилегированная сторона», которая имеет доступ к файловой системе, сети и среде, реализована в Rust. Для тех, кто не знаком с этим языком, Rust — это язык системного программирования, разработанный Mozilla, с акцентом на безопасность памяти и параллелизма. Он используется в таких проектах, как Servo. Бэкэнд Rust перенесен из Go, который послужил для создания оригинального прототипа Deno, представленного в июне 2018 года. Причины перехода связаны с опасениями по поводу двойного GC.


V8 — это движок JavaScript / WebAssembly от Google. Написанный на C++, он также используется в частности в Google Chrome и Node.js. V8 не поддерживает TypeScript. Вместо этого весь код TypeScript, который вы запускаете в Deno, компилируется в JavaScript с помощью компилятора снимков TS, а сгенерированные файлы хранятся в папке .deno. Если пользователь не обновит код, после начальной компиляции будут запускаться только кэшированные файлы JS.


Tokio — это асинхронная среда исполнения для Rust. Он используется для создания и обработки событий. Это позволяет Deno порождать задачи во внутреннем пуле потоков и получать уведомления для обработки вывода после завершения задачи. Tokio опирается на Rust Future, конструкцию, похожую на Promise в JavaScript.


Не могу не упомянуть про сериализацию между TS и Rust. Ранее, для этого использовался Flutbuffers, и это казалось хорошим решением. Но со временем использование такого большого инструмента, с кучей функционала, сошла на нет. Поэтому, было принято решение о написании своего сериализатора. Подробнее о сериализоторах в Deno можно посмотреть в этом видео: https://www.youtube.com/watch?v=qt7fbmypAFk


Функциональность


Прежде всего, нужно упомянуть командную строку, и стандартный CLI Deno. Выполнив команду deno help, deno [subcommand] -- help вы увидите следующее:


>deno help
deno 1.0.0-rc2
A secure JavaScript and TypeScript runtime

Docs: [https://deno.land/std/manual.md](https://deno.land/std/manual.md)
Modules: [https://deno.land/std/](https://deno.land/std/) [https://deno.land/x/](https://deno.land/x/)
Bugs: [https://github.com/denoland/deno/issues](https://github.com/denoland/deno/issues)

To start the REPL:
  deno

To execute a script:
  deno run [https://deno.land/std/examples/welcome.ts](https://deno.land/std/examples/welcome.ts)

To evaluate code in the shell:
  deno eval "console.log(30933 + 404)"

USAGE:
    deno [OPTIONS] [SUBCOMMAND]

OPTIONS:
    -h, --help                     Prints help information
    -L, --log-level <log-level>    Set log level [possible values: debug, info]
    -q, --quiet                    Suppress diagnostic output
    -V, --version                  Prints version information

SUBCOMMANDS:
    bundle         Bundle module and dependencies into single file
    cache          Cache the dependencies
    completions    Generate shell completions
    doc            Show documentation for a module
    eval           Eval script
    fmt            Format source files
    help           Prints this message or the help of the given subcommand(s)
    info           Show info about cache or info related to source file
    install        Install script as an executable
    repl           Read Eval Print Loop
    run            Run a program given a filename or url to the module
    test           Run tests
    types          Print runtime TypeScript declarations
    upgrade        Upgrade deno executable to given version

ENVIRONMENT VARIABLES:
    DENO_DIR             Set deno's base directory (defaults to $HOME/.deno)
    DENO_INSTALL_ROOT    Set deno install's output directory
                         (defaults to $HOME/.deno/bin)
    NO_COLOR             Set to disable color
    HTTP_PROXY           Proxy address for HTTP requests
                         (module downloads, fetch)
    HTTPS_PROXY          Same but for HTTPS

Дополню здесь о существовании флага -unstable, который дает возможность использовать нестабилизированные возможности из Deno core API.


Безопасность


Deno безопасен по умолчанию. Для сравнения, Node.js имеет полный доступ к вашей файловой системе и сети.


Чтобы запустить программу без разрешений достаточно выполнить команду


deno run main.ts 

Если ваш код потребует разрешения, вы получите следующую ошибку:


error: Uncaught PermissionDenied: ...

Deno использует параметры командной строки, чтобы явно разрешить доступ к различным частям системы. Наиболее часто употребляемые это:


  • environment
  • сеть
  • файловая система
  • запуск дочерних процессов

Рекомендуется разрешать только то, что действительно необходимо. Например, чтобы разрешать чтение только с определенной директории, можно воспользоваться флагом -allow-read, а чтобы по сети была возможность ходить на определенные url флаг -allow-net


deno run --allow-read=/path --allow-net=localhost:4545 file.ts

Если вы устанете набирать каждый раз все флаги, то можете записать это в bash script или же воспользоваться флагом -A ( -- allow-all), что крайне не рекомендую. Или же вам приглянется Drake — аналог make только для Deno.


Также, вы можете установить deno программу, воспользовавшись deno install с необходимыми разрешениями. После установки ваша программа будет доступна глобально, поскольку будет в $PATH.


Strict: true по умолчанию


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


tsconfig.json


В Deno по умолчанию есть предустановленный конфиг, но вы, как и в обычных приложениях, можете задать свой кастомный конфиг, за исключением некоторых опций. Я же предпочитаю использовать декораторы в typescript, поэтому без кастомного конфига никак. Вот пример запуска с кастомным конфигом:


deno run -c tsconfig.json main.ts

Модули


Одним из основных серьезных отличий Deno от Node.js является то, что Deno использует официальный стандарт модуля ECMAScript, а не устаревший CommonJS. В Node.js es модули появились только в конце 2019 года, с версией 13.2.0, но даже тогда, поддержка осталась незрелая и она по-прежнему включена спорным расширением .mjs. Deno вырывается из прошлого, используя современные веб-стандарты для своей модульной системы.


import * as framework from "https://deno.land/x/alosaur/src/mod.ts";
import { assert } from "https://deno.land/std/testing/mod.ts";

Как вы наверное заметили, в отличии от обычного вашего typescript кода, в путях необходимо указать расширение файла. Это положительно влияет на скорость сборки ваших приложений. Как успел уже заметить Ryan Dahl, не использование расширений при require, было ошибкой N1 при проектировании Node.js.


Так же модули можно разделить на 3 категории: Core, Std, X.


Deno.core


Здесь находится код, который необходим для обеспечения работы c Rust бэкендом.


Deno Standard Modules


https://deno.land/std/ — это набор стандартных модулей, поставляемых разработчиками Deno, что гарантирует их выполнение вместе с Deno. В нем сосредоточены только общий функционал, которого хватит для написания любой программы. За основу была взята стандартная библиотека языка Go. Поэтому, она вполне самодостаточна, ввиду того, что даже функционал тестирования входит в стандартную либу.


Deno X


К этой категории можно отнести все. что было разработано не основной командой Deno, а также любой импорт стороннего кода в свое приложение.


https://deno.land/x — является лишь сокращателем ссылок. И вы можете добавить свою либу в этот реестр, отправив pull request.


JSPM, Pika.dev — сайты, предназначенные в первую очередь для преобразования любого npm пакета в es модуль. Например так:


import HandlebarsJS from ‘https://dev.jspm.io/handlebars@4.7.6';

Пакетный менеджер


Было радикальное переосмысление относительно того, как управление пакетами работает в Deno. Вместо того, чтобы полагаться на центральное хранилище, оно децентрализовано. Любой может разместить пакет где угодно. Есть преимущества и недостатки использования централизованного репозитория, такого как npm, и этот аспект Deno наверняка будет самым противоречивым.


Больше нет package.json, теперь принято использовать deps.ts, в котором вы можете описать необходимые импорты следующим образом:


export { assert } from "https://deno.land/std@v0.42.0/testing/asserts.ts";

Также вы можете использовать флаг -importmap (-unstable), что даст возможность описывать все импорты в одном json файле:


{
  "imports": {
    "moment": "./moment/moment.ts",
    "moment/": "./moment/",
    "lodash": "./lodash/lodash.ts",
    "lodash/": "./lodash/",
    "https://www.unpkg.com/vue/dist/vue.runtime.esm.js": "./vue.ts"
  },
  "scopes": {
    "scope/": {
      "moment": "./scoped_moment.ts"
    }
  }
}

Не поддерживает std модули, но, тем не менее, умеет работать со следующими схемами: file:, http:, https:


Lock file


Deno может хранить и проверять целостность подресурсов модулей, используя небольшой файл JSON. Используя флаг -- lock = lock.json, чтобы включить и указать проверку файла. Чтобы обновить или создать файл, необходимо использовать -- lock = lock.json -- lock-write.


Web standards


Все, что работает в Deno, по умолчанию работает также, как и в браузере, и Deno c самого начала это пытается обеспечить, чтобы не было странных болячек у пользователей Deno. Что я имею в виду:


> reverse=a=>a.sort(n=>1)
> reverse([1,2,3])

- node (10.13.0): [3,2,1]
- deno (0.4.0) [1,2,3]
- chrome (74.0) [1,2,3]
- other browsers: [1,2,3]

Ну и, конечно же, упомяну здесь, что есть fetch.


Да и в целом, можно было бы перечислить того, что нет в Nodejs, а что есть в Deno, но тогда статья превратилась бы в копипаст спеки. Так что можете быть уверенным, что работает в браузере, то скорее всего уже работает в Deno.


Оставлю здесь ссылку на условный Compat Table для deno: https://deno.dev/wpt/


WASM, RUST, Плагины


Стоит упомянуть, что Deno по умолчанию умеет работать с wasm файлами. Например, таким образом сейчас были интегрированы некоторые драйвера для работы с базами данных.


Отдельно хочется сказать про нативные плагины. Поскольку плагины Deno не является первостепенной задачей, его нельзя загрузить напрямую, используя например обычный import, да и это было бы странно, ведь в браузере вы не имеете такой возможности, но тем не менее есть Deno.openPlugin. Это асинхронная функция, которая автоматически загружает соответствующий двоичный файл, в соответствии с платформой и кэширует его в каталоге .deno_plugins текущего рабочего каталога. Есть нюансы при работе, которую на себя взяла эта библиотека:


https://github.com/manyuanrong/deno-plugin-prepare


Пример:


const pluginOptions: PerpareOptions = {
  name: "test_plugin",

  // Whether to output log. Optional, default is true
  // printLog: true,

  // Whether to use locally cached files. Optional, default is true
  // checkCache: true,

  // Support "http://", "https://", "file://"
  urls: {
    mac: `${releaseUrl}/libtest_plugin.dylib`,
    win: `${releaseUrl}/test_plugin.dll`,
    linux: `${releaseUrl}/libtest_plugin.so`,
  },
};
const rid = await prepare(pluginOptions);
//@ts-ignore
const { testSync } = Deno.core.ops();

const response = Deno.core.dispatch(
  testSync,
  new Uint8Array([116, 101, 115, 116])
)!;

console.log(response);

Собственно, таким образом был реализован Deno webView. Подробнее в статье:


https://denotutorials.net/making-desktop-gui-applications-using-deno-webview.html


Debugging, IDE


В Deno есть встроенная отладка, но на момент написания этой статьи расширение Visual Studio Code его не поддерживает. Для отладки вручную необходимо сделать следующее:


deno run -A -- inspect-brk fileToDebug.ts

Открыть в Chrome chrome://inspect, увидеть Target Deno, и нажать нажать Inspect. Это даст вам отладку в браузере.


В Deno встроен механизм наблюдения за файлами с использованием библиотеки уведомлений Rust через API Deno.watchFs (). Deno любит оставлять тяжелую работу за кулисами с помощью своих API и позволяет пользователю реализовывать свой код так, как ему нравится. Если вам необходим флаг -- watch, то реализация этого кода оставлена на вас.


На данный момент самым популярным средством для разработки является Visual Studio Code c расширением justjavac.vscode-deno


Если же вам необходимо такое же в WebStorm, то обязательно поставьте star в трекэре jetbrains https://youtrack.jetbrains.com/issue/WEB-41607


Тестирование


Средство тестирования встроено в ядро ​​Deno с помощью функции Deno.test(). Функции проверки, такие как assert, assertEquals и т.д. вынесены в стандартную библиотеку. Поэтому, код теста должен выглядеть следующим образом:


import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts";

Deno.test({
  name: "Test name",
  fn(): void {
    assert("test" === "test");
    assertEquals("test", "test");
  },
});

Для запуска остается лишь набрать в консоли:


deno test

Более подробно обо всех возможностях можете увидеть в документации.


Интеграционное тестирование


Начав разработку своего фреймворка на Deno (https://github.com/alosaur/alosaur), и увеличивая функционал на нем, необходимость в интеграционных тестах резко возросла. К счастью, в Deno уже были наработки таких тестов. Для реализация используется функция для запуска дочерних процессов — Deno.run(). Есть особенность таких тестов: необходимо освобождать ресурсы после их прохождения. Как пример можете подсмотреть данное решение: https://github.com/alosaur/alosaur/tree/master/e2e


Compiler API


Deno поддерживает в рантайме доступ к встроенному компилятору Typescript. В пространстве имен Deno есть три метода, которые обеспечивают этот доступ: Deno.compile() Deno.bundle() Deno.transpileOnly()


compile() — это если бы вы вызвали tsc посредством Nodejs,


bundle() — тоже самое как и compile, за исключением того что вместо возврата файлов, он возвращает единственную строку, представляющий из себя es модуль, который включает в себя весь код который был включен в момент компиляции. Эта команда доступна также из CLI deno bundle.


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


export { foo } from "./foo.js";

export const bar = "bar";

Это может быть импортировано :


import { foo, bar } from "./lib.bundle.js";

Bundle также могут быть загружены в веб-браузере. Bundle является автономным модулем ES, поэтому атрибут type должен быть установлен как «module». Например:


<script type="module">
  import * as website from "website.bundle.js";
</script>

transpileOnly() — это то же самое, что и transpileModule в Typescript. Все, что он делает, это «стирает» любые типы из модулей и генерирует JavaScript. Нет проверки типов и разрешения зависимостей.


dev_server


Именно с помощью transpileOnly был реализован dev_server (https://github.com/zhmushan/dev_server) — утилита для запуска вашего фронтенд кода на deno.


Как он работает?
При обращении на index.html, запрашивается main.ts,


Он же транспаилится (в соответствии с заданным tsconfig), и выдает готовый js код по этому url.


Что с зависимостями?
А вот тут конечно приходит на помощь jspm, cdn.pika, unpkg. Поэтому, вам придется указать соответствующий importmap.


В итоге, у вас на выходе JIT, как оно и было во времена systemjs.
Из плюсов: вам не придется ждать пока произойдет dev сборка в консоли.


Но тут, конечно же, не учтены различные препроцессоры, о которых сейчас большинство из нас даже не задумывается, используя Angular CLI


Как попробовать:


deno run -- allow-net -- allow-read -- allow-write -- unstable
https://deno.land/x/dev_server/mod.ts — template angular


Кстати, не зря в качестве примера привел именно Angular, потому как являюсь Angular фанатиком и большую часть времени разрабатываю на нем, и делюсь об этом в своем telegram канале https://t.me/ngFanatic.


CI, CD


Конечно, для эффективной разработки, нельзя бы было обойтись без грамотно построенной доставки приложений. Например сам Deno перевели на github actions. Ранее же, Ryan Dahl советовал мне использовать travis, потому как на тот момент (более года назад), ничего то и не было. Но так как github actions настолько плотно интегрированы, и к тому же показали себя быстрым в развертывании, выбор стал очевидным. Мой конфиг можно посмотреть тут: https://github.com/alosaur/alosaur/blob/master/.github/workflows/ci.yml


Разное


Deno install


Deno предоставляет deno install для простой установки и распространения исполняемого кода. Эта команда создает исполняемый скрипт, который вызывает deno, используя указанные флаги CLI.


$ deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts
[1/1] Compiling https://deno.land/std/http/file_server.ts

 Successfully installed file_server.
/Users/deno/.deno/bin/file_server

И уже в любом месте можно будет запустить этот файловый сервер, набрав в консоли file_server.


Подробнее: https://github.com/denoland/deno/blob/master/docs/tools/script_installer.md


Deno.run()


Я уже упоминал выше в секции интеграционные тесты, про запуск дочерних процессов, стоит отметить что она не только для запуска deno процессов, но и любых процессов, например python:


// --allow-run
const cmd = Deno.run({
  cmd: ["python3", "test.py"], 
  stdout: "piped",
  stderr: "piped"
});

const output = await cmd.output();
const outStr = new TextDecoder().decode(output);

const error = await p.stderrOutput();
const errorStr = new TextDecoder().decode(error);

cmd.close();

console.log(outStr, errorStr);

Deno doc


Пример использования в рантайме можете наблюдать на сайте doc.deno.land. Под капотом юзается typescript type checker (https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker) и дополнительно вытаскивает все, что было указано в формате JSdoc.


Deno format


Deno поставляется со встроенным средством форматирования кода, которое автоматически форматирует код JavaScript и Typescript. Под капотом этого Prettier.


Deno playground


Если вы хотите попробовать запустить deno не устанавливая, то есть интересные песочницы: https://deno.town и https://deno-play.app


Deno awesome


Здесь собрана большая часть информации про Deno, и если же у вас есть чем поделиться, то обязательно добавляйте!


https://github.com/denolib/awesome-deno


Русскоязычное сообщество


Надеюсь, своей статьей смог привлечь вас попробовать Deno уже прямо сейчас. А если у вас будут возникать вопросы, то приглашаю всех в наш уютный чат в телеграмме:


https://t.me/denoland


А если же вам нужна актуальная информация, дайджест про Deno, то для этого создан канал Deno Fanatic.


https://t.me/denoFanatic


p.s.


Более года разрабатываю свой веб фреймворк на декораторах, буду рад любым отзывам:


https://github.com/alosaur/alosaur

Tags:
Hubs:
+15
Comments 14
Comments Comments 14

Articles