Deno 1.9.0 がリリースされたので新機能や変更点の紹介
Copyright (c) 2018-2021 the Deno authors. MIT License.
日本時間の今日(2021 年 4 月 14 日)に Deno の v1.9.0 がリリースされました。
詳細なリリース内容は上記のリリースノートにまとまっていますが、ざっと紹介していきたいと思います。
- ネイティブ HTTP/2 Webサーバー
-
serde_v8
による Rust 呼び出しの高速化 - Blob URLのサポート
- LSP で
import
の補完ができるようになった -
--allow-env
と--allow-run
で許可対象を指定できるようになった - インタラクティブなパーミッションプロンプト
-
Deno.listenTls
で ALPN をサポート - ファイルシステムに関するいくつかのAPIの安定化
- いくつかのAPIを Deprecated 化 (
std
配下に移動) - 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% 向上したとのことです。
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 のリクエストとレスポンスを表す Request
と Response
オブジェクトは fetch()
API と同じものです。また、Request
Response
はともにストリームとして扱えるボディをもっていて、クライアントとの通信を完全に多重化することができます。
serde_v8
による Rust 呼び出しの高速化
2. 改めてになりますが、Deno のコアは Rust で実装されています。上記のように TypeScript (JavaScript) で "Hello World" サーバーを書いて実行したら、内部では Rust で書かれた処理が走るということです。ここで、TypeScript と Rust の世界をデータが行き来する必要がでてきます。このデータのやり取りで、従来は送りたいデータを JSON, FlatBuffers, あるいは独自のバイナリエンコード方式などでエンコードして送る、ということをやっていました。このエンコードは、パフォーマンス上のボトルネックになっているだけではなく、実装が複雑になったり断片化してしまったり、といった問題を抱えていました。
そんなとき、@AaronO さんが「JSONとかを経由してやり取りするんじゃなくて、v8[1] と Rust の値とを直接変換したらめっちゃ効率良くなるのでは?」と提案しました。提案された当初は懐疑的な見方もあったものの、PoCとして serde_v8
が誕生し、パフォーマンス測定をしてみると、驚くほどのパフォーマンス改善が見られるということが分かりました。
以下のグラフは、"Op" [2] をいくつか取り上げ、どれくらいのパフォーマンス向上が得られたかを示したものです。(縦軸の単位は ns)
https://deno.com/blog/v1.9 より引用
また、もう少しレイヤーをあげて、ユーザーが書くコードに近い部分のパフォーマンスも、著しく改善されました。
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!
import
の補完ができるようになった
4. LSP で Deno 1.8 で Deno 組み込みの Language Server Protocol 実装が追加されましたが、続々と新機能が追加されたり改善されたりしています。今回は import の補完機能に改善が施されました。以下に対する補完が効くようになっています:
- ローカルにあるファイル
- ダウンロード済みで環境変数
DENO_DIR
にキャッシュされているライブラリ -
deno.land/x
に登録されているライブラリ
https://deno.com/blog/v1.9 より引用
deno.land/x
に対する補完を有効にするには、エディタの設定ファイルに以下のように書く必要があります。
{
"deno": {
"suggest": {
"imports": {
"hosts": {
"https://deno.land": true
}
}
}
}
}
現在のところ deno.land/x
への補完のみが利用可能ですが、他のパッケージレジストリもレジストリ側の対応がなされれば補完が可能になります。特に Skypack は近いうちにサポートされる予定とのことです。
他には、textDocument/foldingRange
と textDocument/selectionRange
という2つの機能も追加され、コードの折りたたみができるようになる、範囲選択しやすくなる、などの機能が利用可能になります。
多くのバグ修正も含まれています。特に、Windows で file://
という URL が出現したときに異常終了するというバグが修正されました。
--allow-env
と --allow-run
で許可対象を指定できるようになった
5. 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
だけ付けて実行したら、パーミッションが必要な操作に出会う度にプロンプトが出るので、怪しい操作をしようとしていないかを確認しながら実行を進めることができます。
https://deno.com/blog/v1.9 より引用
以下のコマンドで試してみることができます。(筆者注: これも 404 Not Found になりますね……)
$ deno run --prompt https://deno.com/blog/v1.9/prompt_permissions.ts
Deno のコアチームは --prompt
についてのフィードバックを募っています。将来的に --prompt
フラグをデフォルトで有効とすることが検討されています。
Deno.listenTls
で ALPN をサポート
7. 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 へ移行しましょう。
useDefineForClassFields
オプションを有効化
10. TypeScript の TypeScript の useDefineForClassFields
オプションが有効化されました。このオプションにより、クラスのフィールドの扱いが ECMAScript のセマンティクスに準拠するようになります。ほとんどのユーザーのコードには影響しません。
おわり
以上、1.9.0 の紹介でした。
最近 Deno Deploy がローンチされたり Deno Company のアナウンスがされたりと、ますます盛り上がりを見せている Deno ですが、今回のリリースもパフォーマンスの劇的な向上などかなりエキサイティングなリリースでした。
次のマイナーバージョン 1.10.0 のリリースは 2021 年 5 月 11 日の予定です。
過去のリリース情報
1.8.0
1.7.0
1.6.0
-
Deno が使っている JavaScript 実行エンジン: https://v8.dev/ ↩︎
-
TypeScript 側から Rust 側の処理を呼び出す際の実行単位 ↩︎
Discussion