Deno 1.9.0 がリリースされたので新機能や変更点の紹介

10 min read読了の目安(約9400字

deno illust
Copyright (c) 2018-2021 the Deno authors. MIT License.

日本時間の今日(2021 年 4 月 14 日)に Deno の v1.9.0 がリリースされました。

https://deno.com/blog/v1.9

詳細なリリース内容は上記のリリースノートにまとまっていますが、ざっと紹介していきたいと思います。

  1. ネイティブ HTTP/2 Webサーバー
  2. serde_v8 による Rust 呼び出しの高速化
  3. Blob URLのサポート
  4. LSP で import の補完ができるようになった
  5. --allow-env--allow-run で許可対象を指定できるようになった
  6. インタラクティブなパーミッションプロンプト
  7. Deno.listenTls で ALPN をサポート
  8. ファイルシステムに関するいくつかのAPIの安定化
  9. いくつかのAPIを Deprecated 化 (std 配下に移動)
  10. TypeScript の useDefineForClassFields オプションを有効化

1. ネイティブ HTTP/2 Webサーバー

Deno の HTTP 実装は標準ライブラリにあり (std/http) TypeScript で実装が書かれています。それなりには優れたパフォーマンスを示しているものの、HTTP/1.1 にしか対応していないという問題があります。HTTP/2 の対応を目指していましたが、HTTP サーバーを実装するのはかなり大変な作業であり、自前で実装するのではなく既存のネイティブコード実装を採用する道を選びました。Rust の Hyper という HTTP ライブラリが採用されました。

結果として、既存の std/http の実装と比較して、シンプルな "Hello World" サーバーのスループットが48% 向上したとのことです。

http server performance
https://deno.com/blog/v1.9 より引用

この新しい HTTP API は、1.9 では unstable API としての提供です。利用するためには --unstable フラグをつける必要があります。"Hello World" サーバーの実装例は以下です。

const body = new TextEncoder().encode("Hello World");
for await (const conn of Deno.listen({ port: 4500 })) {
  (async () => {
    for await (const { respondWith } of Deno.serveHttp(conn)) {
      respondWith(new Response(body));
    }
  })();
}

HTTP のリクエストとレスポンスを表す RequestResponse オブジェクトは fetch() API と同じものです。また、Request Response はともにストリームとして扱えるボディをもっていて、クライアントとの通信を完全に多重化することができます。

2. serde_v8 による Rust 呼び出しの高速化

改めてになりますが、Deno のコアは Rust で実装されています。上記のように TypeScript (JavaScript) で "Hello World" サーバーを書いて実行したら、内部では Rust で書かれた処理が走るということです。ここで、TypeScript と Rust の世界をデータが行き来する必要がでてきます。このデータのやり取りで、従来は送りたいデータを JSON, FlatBuffers, あるいは独自のバイナリエンコード方式などでエンコードして送る、ということをやっていました。このエンコードは、パフォーマンス上のボトルネックになっているだけではなく、実装が複雑になったり断片化してしまったり、といった問題を抱えていました。

そんなとき、@AaronO さんが「JSONとかを経由してやり取りするんじゃなくて、v8[1] と Rust の値とを直接変換したらめっちゃ効率良くなるのでは?」と提案しました。提案された当初は懐疑的な見方もあったものの、PoCとして serde_v8 が誕生し、パフォーマンス測定をしてみると、驚くほどのパフォーマンス改善が見られるということが分かりました。

以下のグラフは、"Op" [2] をいくつか取り上げ、どれくらいのパフォーマンス向上が得られたかを示したものです。(縦軸の単位は ns)

serde_v8 baseline improvement
https://deno.com/blog/v1.9 より引用

また、もう少しレイヤーをあげて、ユーザーが書くコードに近い部分のパフォーマンスも、著しく改善されました。

serde_v8 common Deno functions improvement
https://deno.com/blog/v1.9 より引用

3. Blob URLのサポート

blob: 形式の URL サポートが追加されました。ブラウザで行うのと同様に、以下のようなコードが動作します。

const blob = new Blob(["Hello World!"]);
const url = URL.createObjectURL(blob);
console.log(url); // blob:null/7b09af21-03d5-461e-90a3-af329667d0ac

const resp = await fetch(url);
console.log(await resp.text()); // Hello World!

URL.revokeObjectURL(url);

Blob URL は以下のようなケースでパラメータとして使うことができます。

  • fetch API
  • new Worker による Web Worker 作成
  • import() による動的インポート

また、Blob URL に加えて、data URL も fetch API で利用することができるようになりました。

const resp = await fetch("data:text/plain;base64,SGVsbG8gV29ybGQh");
console.log(await resp.text()); // Hello World!

4. LSP で import の補完ができるようになった

Deno 1.8 で Deno 組み込みの Language Server Protocol 実装が追加されましたが、続々と新機能が追加されたり改善されたりしています。今回は import の補完機能に改善が施されました。以下に対する補完が効くようになっています:

  • ローカルにあるファイル
  • ダウンロード済みで環境変数 DENO_DIR にキャッシュされているライブラリ
  • deno.land/x に登録されているライブラリ

LSP completion demo
https://deno.com/blog/v1.9 より引用

deno.land/x に対する補完を有効にするには、エディタの設定ファイルに以下のように書く必要があります。

{
  "deno": {
    "suggest": {
      "imports": {
        "hosts": {
          "https://deno.land": true
        }
      }
    }
  }
}

現在のところ deno.land/x への補完のみが利用可能ですが、他のパッケージレジストリもレジストリ側の対応がなされれば補完が可能になります。特に Skypack は近いうちにサポートされる予定とのことです。

他には、textDocument/foldingRangetextDocument/selectionRange という2つの機能も追加され、コードの折りたたみができるようになる、範囲選択しやすくなる、などの機能が利用可能になります。

多くのバグ修正も含まれています。特に、Windows で file:// という URL が出現したときに異常終了するというバグが修正されました。

5. --allow-env--allow-run で許可対象を指定できるようになった

Deno は許可する操作を明示的にフラグで渡さなければならないという特徴がありますが、いくつかのフラグは許可対象をリストで指定することができます。例えば --allow-read=/tmp と指定すると、/tmp ディレクトリに対する読み取り許可のみを与えることができます。

これまで、--allow-env--allow-run は「すべてを許可するか、何も許可しないか」の2択でした。つまり、--allow-env を指定した場合は環境変数に対してのすべてのアクセスが許可されるし、--allow-run の場合はいかなるプログラムに対するサブプロセスであっても起動してもよいという許可を与えることになる、ということです。

1.9 からは、--allow-env--allow-run に対しても細かな指定を行うことができるようになりました。以下がその一例です:

$ deno run --allow-env=DEBUG,LOG https://deno.com/blog/v1.9/env_permissions.ts
$ deno run --allow-run=deno https://deno.com/blog/v1.9/run_permissions.ts

(筆者注: ↑ が原文にも書かれているのですが、スクリプトのURLが間違っているのか、動かないですね……)

また、実行時にパーミッションを確認するための API Deno.permissions.query() を使って、あるバイナリを実行する許可が付与されているのかどうかを確認できるようになりました。

// `deno` を実行するパーミッションが与えられているかを確認する
await Deno.permissions.query({ name: "run", command: "deno" });

6. インタラクティブなパーミッションプロンプト

Deno は、適切なパーミッションが付与されていない状態でそのパーミッションが必要なスクリプトを実行すると、その場でエラーを吐いて終了します。

1.9 では --prompt というフラグが追加されました。このフラグをつけると、プログラム実行時、パーミッションが必要になるとその都度インタラクティブに許可するかどうかを選択できるようになります。

--prompt は特にインターネット上からとってきた1回限りのスクリプトを実行したいときに有用です。スクリプトの実行前に必要なパーミッションをすべて把握しておく必要はありません。パーミッションを何も与えずに --prompt だけ付けて実行したら、パーミッションが必要な操作に出会う度にプロンプトが出るので、怪しい操作をしようとしていないかを確認しながら実行を進めることができます。

interactive permission prompt
https://deno.com/blog/v1.9 より引用

以下のコマンドで試してみることができます。(筆者注: これも 404 Not Found になりますね……)

$ deno run --prompt https://deno.com/blog/v1.9/prompt_permissions.ts

Deno のコアチームは --prompt についてのフィードバックを募っています。将来的に --prompt フラグをデフォルトで有効とすることが検討されています。

7. Deno.listenTls で ALPN をサポート

HTTP/2 は Unix ソケット、TCP ソケット、あるいは TLS を利用した通信などの上で利用することができます。

主要な Web ブラウザは TLS コネクション上での HTTP/2 のみをサポートしており、TLS ハンドシェイクの過程で「HTTP/2 をサポートしています」ということをサーバー側に知らせます。このプロトコル選択は "Application-Layer Protocol Negotiation" (ALPN) と呼ばれています[3]。これによってクライアントとサーバーが TLS コネクションの上でどのプロトコルを使って通信を行うかということを交渉できるようになります。

Deno 1.9 では HTTP/2 サーバーを書くことができるようになった、ということは上で書きました。これに合わせて、ALPN に対応し、「サーバー側は HTTP/2 に対応していますよ」ということをクライアント側に伝えるようにしなければなりません(そうしないとせっかく HTTP/2 に対応していても HTTP/1.1 が使われてしまう)。
そこで、Deno.listenTls を使って TLS リッスンを開始する際に、 ALPN プロトコルの一覧を指定することができるようになりました。

const listener = Deno.listenTls({
  port: 443,
  certFile: "./cert.pem",
  keyFile: "./key.pem",
  // `h2` は HTTP/2 over TLS を意味する識別子
  alpnProtocols: ["h2", "http/1.1"],
});

for await (const conn of listener) {
  handleConn(conn);
}

async function handleConn(conn: Deno.Conn) {
  const httpConn = Deno.serveHttp(conn);
  for await (const { request, respondWith } of httpConn) {
    respondWith(new Response(`Responding to ${request.url}`));
  }
}

8. ファイルシステムに関するいくつかのAPIの安定化

ファイルシステムに関する以下の API が安定化されました。

  • Deno.fstat
  • Deno.fstatSync
  • Deno.ftruncate
  • Deno.ftruncateSync

また、Deno.File クラスに以下のメソッドが追加されました。

  • File.stat
  • File.statSync
  • File.truncate
  • File.truncateSync

9. いくつかのAPIを Deprecated 化 (std配下に移動)

Deno 向けに書かれたコードをブラウザなどの他の環境でも実行できるようにするということを目指す中で、Deno 名前空間に存在するいくつかの API を標準ライブラリ std に移行することが決定されました。移行に先駆けて、1.9 では以下の7個の API が "deprecated" となりました。

  • Deno.Buffer
  • Deno.readAll
  • Deno.readAllSync
  • Deno.writeAll
  • Deno.writeAllSync
  • Deno.iter
  • Deno.iterSync

これらは std/io モジュールに移動しています(すでに利用可能)。また、もし上記の "deprecated" API を利用している場合にそれを警告してくれるリントルールが合わせて追加されています。

(no-deprecated-deno-api) `Buffer` is deprecated and scheduled for removal in Deno 2.0
const b = new Deno.Buffer();
              ^^^^^^^^^^^
    at /tmp/foo.ts:1:14

    hint: Use `Buffer` from https://deno.land/std/io/buffer.ts instead

これらの API は Deno 2.0 にて完全に削除される予定です。なるべく早めに標準ライブラリの代替 API へ移行しましょう。

10. TypeScript の useDefineForClassFields オプションを有効化

TypeScript の useDefineForClassFields オプションが有効化されました。このオプションにより、クラスのフィールドの扱いが ECMAScript のセマンティクスに準拠するようになります。ほとんどのユーザーのコードには影響しません。

おわり

以上、1.9.0 の紹介でした。
最近 Deno Deploy がローンチされたり Deno Company のアナウンスがされたりと、ますます盛り上がりを見せている Deno ですが、今回のリリースもパフォーマンスの劇的な向上などかなりエキサイティングなリリースでした。

次のマイナーバージョン 1.10.0 のリリースは 2021 年 5 月 11 日の予定です。

過去のリリース情報

1.8.0

https://zenn.dev/magurotuna/articles/deno-release-note-1-8-0
1.7.0

https://zenn.dev/magurotuna/articles/55575eb16ae422
1.6.0

https://zenn.dev/magurotuna/articles/020f6b103937ed
脚注
  1. Deno が使っている JavaScript 実行エンジン: https://v8.dev/ ↩︎

  2. TypeScript 側から Rust 側の処理を呼び出す際の実行単位 ↩︎

  3. RFC7301 https://tools.ietf.org/html/rfc7301 ↩︎