🦕

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

2021/07/14に公開

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

日本時間の昨日(2021 年 7 月 13 日)に Deno の v1.12.0 がリリースされました。

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

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

  1. generateKey, sign, verify の3つの Web Crypto API がサポート
  2. ネイティブ HTTP サーバーでの WebSocket サポート
  3. REPL における TypeScript のサポート
  4. MessagePort および MessageChannel のサポート
  5. WebAssembly.compileStreaming()WebAssembly.instantiateStreaming() のサポート
  6. Web Worker 間で AtomicsSharedArrayBuffer を共有することができるようになった
  7. FinalizationRegistryWeakRef の一部バグが修正
  8. Chrome の開発者ツールにおけるデバッグ体験が改善
  9. deno test の改良
  10. fetch でカスタムプロキシのサポート
  11. Deno.readFileAbortSignal による実行の中断が可能になった
  12. Deno.copy が非推奨化
  13. tsconfig の types オプションが利用可能になった
  14. JavaScript の新しい言語機能が利用可能になった
  15. Language Server の改良
  16. Deno 名前空間を書き換えることが可能になった
  17. 動的インポートと循環参照に関するバグの修正
  18. Web プラットフォーム互換性のステータスが wpt.fyi で閲覧可能になった
  19. 2021 Q3 のロードマップ公開

もりだくさんなので、それぞれサクッと書いていきます。

1. generateKey, sign, verify の3つの Web Crypto API がサポート

前回 v1.11 のリリースで予告されていた通り、Web Crypto API の実装がさらに進みました。今回追加されたのは

の 3 つです。

key を生成する generateKey

const keyPair = await crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-384" },
  true,
  ["sign", "verify"],
);

署名する sign

const data = new TextEncode.encode("hello world");
const signature = await crypto.subtle.sign(
  { name: "ECDSA", hash: "SHA-256" },
  keyPair.private,
  data,
);

署名を検証する verify

const isValid = await crypto.subtle.verify(
  { name: "ECDSA", hash: "SHA-256" },
  keyPair.public,
  signature,
  data,
);

次回のリリースでさらに多くの Web Crypto API をサポートする予定です。

2. ネイティブ HTTP サーバーでの WebSocket サポート

Deno 1.9.0 にて、それまで TypeScript で書かれていた HTTP 実装にかわり、Rust の [hyper] というライブラリを採用したネイティブ実装が追加されました。(参考)
その続きとして、今回のバージョンから、WebSocket も Rust によるネイティブ実装が追加されました。
(なお、現在このネイティブ HTTP 実装は unstable となっており --unstable フラグが必要ですが、次のバージョン 1.13 にて安定化が予定されています)

この新しい WebSocket API の使い方の例は以下のとおりです。

async function handleConn(conn) {
  const httpConn = Deno.serveHttp(conn);
  for await (const e of httpConn) {
    e.respondWith(handle(e.request));
  }
}

function handle(req) {
  if (req.headers.get("upgrade") != "websocket") {
    return new Response("not trying to upgrade as websocket.");
  }
  const { websocket, response } = Deno.upgradeWebSocket(req);
  websocket.onopen = () => console.log("socket opened");
  websocket.onmessage = (e) => {
    console.log("socket message:", e.data);
    websocket.send(new Date().toString());
  };
  websocket.onerror = (e) => console.log("socket errored:", e.message);
  websocket.onclose = () => console.log("socket closed");
  return response;
}

const listener = Deno.listen({ port: 8080 });
console.log("listening on http://localhost:8080");
for await (const conn of listener) {
  handleConn(conn);
}

上記のコードは deno run --allow-net --unstable https://deno.com/v1.12/ws_server.js で実行できます。これを立ち上げたまま、REPL で以下のように実行すると、WebSocket が疎通していることが確認できます。

REPL
> const ws = new WebSocket("ws://localhost:8080/");
> ws.onmessage = (e) => console.log(e.data);
> ws.send("hi");

3. REPL における TypeScript のサポート

これまで REPL では JavaScript のコードしか動かすことができませんでした。つまり、以下のような TypeScript のコードを書くと、構文エラーとなってしまっていました。

以前の挙動
$ deno
Deno 1.11.5
exit using ctrl+d or close()
> function log(message: string) { console.log(message) }
Uncaught SyntaxError: Unexpected token ':'

簡易計算機のように利用する場合は JavaScript だけ動けば問題ないことが多いですが、どこかから持ってきたサンプルコードをコピペして実行したい、といったようなケースでは、TypeScript が実行できると嬉しいです。
そこで、今回のリリースから、TypeScript コードが REPL 上で実行できるようになりました。上で構文エラーとなっていたコードも、1.12.0 では問題なく動作します:

現在の挙動
$ deno
Deno 1.12.0
exit using ctrl+d or close()
> function log(message: string) { console.log(message) }
undefined

ただし、あくまで TypeScript の部分を無視して実行しているだけであり、型チェックが実行されているわけではないということに注意してください。

また、この他に REPL に関連して 2 点の改善がなされています。

静的 import のサポート

これまで REPL 上では静的な import をすることができませんでした。つまり

静的 import
import { serve } from "https://deno.land/std@0.101.0/http/server.ts";`

のように書くことができませんでした。この代わりに、

動的 import
const { serve } = await import('https://deno.land/std@0.101.0/http/server.ts');

のように動的 import を行う必要がありました。

今回のバージョンから、静的 import を行うことができるようになりました。内部的には静的 import を動的 import へと変換して実行しています。

タブ補完ですべての候補を一覧表示するようになった

REPL 上で Tab キーを押すと、プロパティやメソッドなどが補完される機能があります。便利な機能ですが、今までは Tab キーを押すたびに候補が1つずつ入力されるだけだったので、補完候補が大量にある場合にはなかなか目当ての補完にたどり着けませんでした。

今回のバージョンからは、補完候補が一覧表示されるようになり、そのオブジェクトがどのようなプロパティをもつのかをひと目で確認できるようになりました。

詳しくは以下の動画をご覧ください:

https://deno.com/v1.12/repl_completions.mp4

4. MessagePort および MessageChannel のサポート

MessagePort および MessageChannel がサポートされます。これらを使うと、複雑なオブジェクトを相互にコピーしたり転送したりできるような MessagePort を作ることができ、ワーカーやメインスレッドの間でデータの送受信を行う「ソケット」のように機能させることができます。

この仕組みを活用したライブラリに comlink があります。これは MessagePort をラップして、ワーカー内の処理をシンプルな RPC インターフェースとしてメインスレッドから利用できるようにしてくれます。以下の例では、Web Worker 内のカウンターを Comlink を利用してメインスレッドへと公開し、インクリメントしています:

https
import * as Comlink from "https://cdn.skypack.dev/comlink@4.3.1?dts";

// ./worker.js のコードをワーカーとして起動
const url = new URL("./worker.js", import.meta.url);
const worker = new Worker(url, { type: "module" });

// ワーカーを comlink でラップ
const obj = Comlink.wrap(worker);

// あたかもローカル変数かのように、ワーカー内のオブジェクトの
// メソッド実行・プロパティ読み出しを行うことができる
console.log(`Counter: ${await obj.counter}`);
await obj.inc();
console.log(`Counter: ${await obj.counter}`);

worker.terminate();
https
import * as Comlink from "https://cdn.skypack.dev/comlink@4.3.1?dts";

// メインスレッドへ公開するオブジェクトを作る
// 現在のカウント値と、それをインクリメントするメソッドから成る
const obj = {
  counter: 0,
  inc() {
    this.counter++;
  },
};

// カウンターをホスト側へと公開する
Comlink.expose(obj);

5. WebAssembly.compileStreaming()WebAssembly.instantiateStreaming() のサポート

Deno は 1.0 から WebAssembly をサポートしていますが、これまではバッファに読み込み済みのもののインスタンス化、すなわち WebAssembly.compile() および WebAssembly.instantiate() のみをサポートしていました。

今回のバージョンから、以下の 2 API もサポートされるようになります:

これらの追加 API は Response または Promise<Response> を引数にとることができ、.wasm をストリームとして読み込み、インスタンス化できます。

6. Web Worker 間で AtomicsSharedArrayBuffer を共有することができるようになった

JavaScript はシングルスレッドで動作するよう設計された言語で、すべてのオブジェクトやプリミティブ値、そして関数はシングルスレッドからしかアクセスできません。この制限により、Web Workers API によるマルチスレッドプログラミングを行う際には、ワーカー間のデータのやりとりを複製したメッセージの送受信で行うしかありません。

JavaScript においては SharedArrayBuffer によってスレッド間で可変データを共有することができるようになります。また、これに合わせて、読み書きによるデータ競合が起こらないようにする仕組みとして Atomics も必要です。

今回のバージョンから、SharedArrayBuffer をワーカー間で送り合うことができるようになりました。また、Atomics API もサポートされます。これによって、C, C++, Rust などのコードを、マルチスレッドに対応した WebAssembly へとコンパイルして利用することが期待されています。

WebAssembly におけるスレッドに関しては Using WebAssembly threads from C, C++ and Rust が参考になります。

なお、現時点では SharedArrayBuffer およびこれのビューとしての TypedArrayfetch, Deno.read, TextDecoder などの API に渡すことはできません。これらのサポートは今後追加される予定です。

7. FinalizationRegistryWeakRef の一部バグが修正

これまでもずっと利用可能だった FinalizationRegistry および WeakRef の2 API ですが、いくつかのバグが報告されていました。

今回のバージョンで内部の技術負債の対応がなされ、報告されていたバグが修正されて正しく動作するようになりました。

8. Chrome の開発者ツールにおけるデバッグ体験が改善

Deno はデバッガ機能を組み込んでおり、Chrome の開発者ツールや VSCode の組み込みデバッガなどを用いたリモートデバッグをサポートしています。

今回のバージョンでは、Chrome 開発者ツールにおけるデバッグ体験を向上させる 2 つの修正が入りました。

コンソールメッセージを開発者ツールとターミナルで共有する

これまで、console.log などを使った出力は開発者ツール側のコンソールには出力されませんでした。今回のバージョンからこれが修正されて、アプリケーションコード中の console 系メソッドと、開発者ツールのコンソールで実行した console 系メソッドが、ターミナルと開発者ツールのコンソールの両方に出力されるようになりました。

console message to devtools and terminal
https://deno.com/blog/v1.12 より

about://inspect で表示される Deno プロセスの情報が詳細化

about://inspect でデバッグ中のプロセスを一覧表示することができます。これまではほとんど情報が表示されず、例えば複数の Deno プロセスを立ち上げているときに、どちらがどのプロセスなのかを見分けることが困難でした。今回のバージョンからはプロセス ID および メインモジュールの URL が表示されるようになり、見分けるのが容易になりました。

easily distinguishable list of inspector
https://deno.com/blog/v1.12 より

9. deno test の改良

組み込みのテストランナー deno test にいくつか改良が加えられました。

--shuffle=<SEED> オプションの追加

--shuffle が追加されました。これを使うと、テストケースをランダムな順番で実行することができます。テストケース間に意図しない依存関係ができてしまっていないかを確認するのに便利です。

--fail-test=<N> オプションの追加

deno test コマンドは、デフォルトではテストが1つでも失敗した時点で実行がストップします。今回追加された --fail-test=<N> を使うことで、N 個のテストが失敗するまで実行を続けさせることができます。

10. fetch でカスタムプロキシのサポート

これまで HTTP(S) の上り通信をプロキシするためには HTTP_PROXYHTTPS_PROXYNO_PROXY などの環境変数を用いた方法を利用するしかありませんでした。今回のバージョンからは fetch API のレベルでプロキシを制御することができるようになりました。

以下の例のように、まず Deno.createHttpClient でプロキシを設定した Deno.HttpClient を生成し、それを fetch に渡します。

const client = Deno.createHttpClient({
  proxy: {
    url: "http://myproxy.com:8080",
    basicAuth: { username: "deno", password: "***" },
  },
});
const response = await fetch("https://myserver.com", { client });

11. Deno.readFileAbortSignal による実行の中断が可能になった

ファイルを読み込む Deno.readFileAbortSignal による中断がサポートされました。ファイルのサイズが大きかったり、様々な要因で読み込みに大変な時間がかかる場合に、読み込み処理を中断することができて便利です。

const aborter = new AbortController();
Deno.readFile("./super_large_file.txt", { signal: aborter.signal })
  .then((data) => console.log("File read:", data.length))
  .catch((err) => console.error("File read failed:", err));
setTimeout(() => aborter.abort(), 1000);

12. Deno.copy が非推奨化

Deno.copy が非推奨 API となりました。https://deno.land/std@0.101.0/io/util.ts に同じ機能をもつ関数が移動したので、今後はこちらを利用してください。

なお、Deno.copy を使っている場合は deno lint による警告が出るようになります。

std/io の copy 関数を利用する
import { copy } from "https://deno.land/std@0.101.0/io/util.ts";

const source = await Deno.open("my_file.txt");
const destination = await Deno.create("my_file_dest.txt");
await copy(source, destination);

13. tsconfig の types オプションが利用可能になった

Deno は TypeScript の設定のうち、一部をサポートしています。

https://deno.land/manual/typescript/configuration#how-deno-uses-a-configuration-file

今回のバージョンから、types オプションがサポートに追加されました。これにより、任意の型定義ファイルを指定することができるようになり、型チェックの柔軟性が増します。

14. JavaScript の新しい言語機能が利用可能になった

Deno に組み込まれている JavaScript エンジン V8 のバージョンが 9.2 に上がりました。これに伴い、JavaScript の新しい機能が利用可能になりました。

Array, String, TypedArray で at() メソッドが使えるようになった

Array, String, TypedArray (Uint8Array など) に at() というメソッドが追加されました。これは通常のインデックスアクセス [] と似ていますが、負のインデックスを受け取れる点が異なります。

let arr = [1, 2, 3, 4];
arr.at(-1); // Returns 4

Intl.DateTimeFormatdayPediod オプションが追加

Intl.DateTimeFormatdayPeriod オプションが追加され、Date を以下のような文字列にフォーマットして出力することができるようになりました。

  • 7 in the morning
  • 12 noon
  • 12時
const date = new Date(Date.UTC(2021, 7, 14, 3, 0, 0, 0));
const options = {
  hour: "numeric",
  dayPeriod: "short",
} as const;
console.log(new Intl.DateTimeFormat("en-US", options).format(date));
// 12 noon と出力される

15. Language Server の改良

毎バージョン恒例ですが、Language Server の安定性向上と機能追加が行われました。特筆すべき2つの新機能を紹介します。

deno lint エラーを ignore するための Quick Fix Action が追加

deno lint のエラーが出ている箇所で、そのエラーを無視するためのコメントを挿入する Quick Fix ができるようになりました。例えば、以下の画像で "Disable prefer-const for this line" を選択すると、// deno-lint-ignore prefer-const が該当の行の直前に挿入されます。

quick fix action to ignore lint errors
https://deno.com/blog/v1.12 より

マウスホバーで依存モジュールの情報を表示

以下のように、import 文の依存モジュール URL 部分をマウスホバーすると、その詳細情報が表示されるようになりました。

lsp_hover_info
https://deno.com/blog/v1.12 より

16. Deno 名前空間を書き換えることが可能になった

Deno はグローバル名前空間として Deno を提供していて、さまざまな API がこの名前空間の下で利用可能です。この DenoObject.freeze() によって凍結されていて、ユーザーが書き換えることは今まで不可能でした。

今回のバージョンからはこの凍結が解除され、ユーザーが実行時に Deno 名前空間を書き換えることができるようになりました。書き換えは多用すべきではありませんが、例えばテストを書くときに Deno 下の API をモックしたい、などの場面で大いに役立つでしょう。

以前の挙動
$ deno
Deno 1.11.5
exit using ctrl+d or close()
> Deno.someProperty = "foo";
Uncaught TypeError: Cannot add property someProperty, object is not extensible
    at <anonymous>:2:8
最新の挙動
$ deno
Deno 1.12.0
exit using ctrl+d or close()
> Deno.someProperty = "foo";
"foo"

17. 動的インポートと循環参照に関するバグの修正

報告されてから長い間未解決だった、動的インポートと循環参照に関するバグが修正されました。これは、並行して動的インポートをしたときにモジュール間の循環参照が含まれていると、実行がハングする、あるいはクラッシュするというバグです。発生状況の詳細についてはこちらの issue をチェックしてください。

このバグが今回のバージョンから修正されました。

18. Web プラットフォーム互換性のステータスが wpt.fyi で閲覧可能になった

Deno は Web 標準への準拠を目指しています。Web API との互換性を高めるための指標として役立つのが、Web Platform Tests (WPT) です。Chrome、Firefox、Safari などのブラウザがこのテストスイートを共有することで、Web API のブラウザ間での互換性が担保されています。

主要ブラウザのテスト結果がまとめられている https://wpt.fyi/ に Deno の結果が掲載されるようになりました。

wpt status on wpt.fyi

19. 2021 Q3 のロードマップ公開

https://github.com/denoland/deno/issues/11168

2021年 Q3 のロードマップが公開されました。フィードバックがある場合はぜひ上記 issue にコメントを。

おわり

以上、1.12.0 の紹介でした。
次のマイナーバージョン 1.13.0 のリリースは 2021 年 8 月 10 日の予定です。

過去のリリース情報

1.11.0

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

1.10.1

https://zenn.dev/magurotuna/articles/deno-release-note-1-10-1

1.9.0

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

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

Discussion