🦕

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

2021/08/12に公開

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

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

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

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

  1. ネイティブ HTTP サーバー実装の安定化
  2. self.structuredClone() のサポート
  3. TLSのためのシステム証明書ストアが利用可能になった
  4. テストのためTLS検証を無効にできるようになった
  5. Web Crypto API のアップデート
  6. Deno Language Server と VS Code 拡張機能のアップデート
  7. REPL の改良
  8. navigator.hardwareConcurrency API のサポート
  9. V8 v9.3 にアップデート
  10. deno info で型宣言ファイルへの扱えるようになった
  11. Deno.writeFileAbortSignal のサポート
  12. Markdown ファイル内のコードに対しての型チェック
  13. きれいな環境の子プロセスを立ち上げることができるようになった
  14. Deno.permissions API が URL を受け付けるようになった
  15. ネイティブプラグイン機構を置き換える FFI の実験的な実装
  16. [WebSocketStream] API の実験的実装

1. ネイティブ HTTP サーバー実装の安定化

Deno 1.9.0 から unstable 機能として実装されていた HTTP サーバーのネイティブ実装 が安定化されました。これにより、従来の std/http に比べて極めて優れたパフォーマンスのHTTPサーバーを、--unstable フラグなしで利用できるようになります。

https://deno.land はここ数週間、この新しい HTTP サーバーをベースとして数百万のリクエストをさばいるという実績があります。

std/http は、しばらくは残りますが、じきに削除される予定です。std/http を利用している場合は新 API に移行することが推奨されます。なお、oak を利用している場合は、シームレスに移行が可能です。

以下は 4500 番ポートで HTTP サーバーを立ち上げるサンプルコードです。

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

新しい API のマニュアルは ここ にあります。

2. self.structuredClone() のサポート

Web Worker や Message Port を介したメッセージのやり取りのために使われる構造化複製アルゴリズム (structured clone algorithm)ですが、これをシンプルかつ同期的に行うことができる API として self.structuredClone() が HTML の仕様に追加されました。これに合わせて、Deno でもこの API の実装が追加され、利用できるようになりました。

構造化複製アルゴリズムを使うと、循環参照が含まれるオブジェクトであってもディープコピーをすることができます。サンプルコードは以下です:

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

// Create an object with a value and a circular reference to itself.
const foo = { bar: "baz" };
foo.foo = foo;

// Clone it
const clone = self.structuredClone(foo);

assert(clone !== foo); // assert they are not the same object
assert(clone.bar === "baz"); // assert they  do have the same value though
assert(clone.foo === clone); // assert that the circular reference is preserved

console.log("All assertions passed!");

以下のコマンドで試すことができます。

$ deno run https://deno.com/v1.13/structured_clone.js

All assertions passed!

3. TLSのためのシステム証明書ストアが利用可能になった

今回のバージョンから、DENO_TLS_CA_STORE 環境変数を通して、システムの証明書ストアを利用できるようになりました。具体的には、DENO_TLS_CA_STORE=system とすることで、例えば macOS では keychain から証明書が読み込まれるようになります。

DENO_TLS_CA_STORE に関する詳細なマニュアルは こちら を参照してください。

4. テストのためTLS検証を無効にできるようになった

--unsafely-ignore-certificate-errors フラグが追加されました。このフラグを付与することで、TLS の証明書検証を無視することができるようになります。

名前が示す通り、このフラグは使い所に注意が必要です。単に証明書エラーを回避するための目的で使用すべきではなく、あくまでテスト用途で使用されるべきです。通常の用途では、--cert フラグ、あるいは上で紹介した DENO_TLS_CA_STORE=system の設定によって適切に TLS 検証に関するエラーに対処すべきです。

5. Web Crypto API のアップデート

ここのところ毎バージョン恒例となっていますが、Web Crypto API の対応がさらに進みました。

6. Deno Language Server と VS Code 拡張機能のアップデート

今回のバージョンでも Deno Language Server の機能が拡充されました。

"Refactoring" コードアクションのサポート

JavaScript および TypeScript のファイルで "Refactoring" コードアクションが利用可能になりました。これにより、コードの一部を関数・定数に抽出したり、新しいファイルにコードを移動させたりといった作業が容易に行えるようになります。

キャッシュディレクトリのパスを明示的に指定できるようになった

deno.cache という設定値をもつようになりました。この値を指定することで、Language Server が使用する Deno のキャッシュディレクトリのパスを変更することができます。

通常キャッシュディレクトリはシステムで共通のものが使われますが、プロジェクトごとにキャッシュを分けたい、といったケースがもしあれば、この機能を活用すると便利でしょう。

7. REPL の改良

deno repl にも 2 つの大きな改善が入りました。

export が無視されるようになった

今まで、REPL では export がサポートされておらず、例えば export const foo = 42; などと書くとエラーとなっていました。しかし今回のバージョンからは export が使われてもエラーにはならず、単に無視されるだけとなりました。

export が含まれるようなコードサンプルをコピペで試してみたいときなどに便利です。

--eval フラグのサポート

--eval フラグが使えるようになりました。このフラグを使うと、REPL の起動時に最初に実行したいコードを実行コマンドの一部として指定できます。例えば、以下のように --eval 'import { assert } from "https://deno.land/std@0.104.0/testing/asserts.ts";' とすることで、REPL 起動時に標準ライブラリから assert をインポートすることができます。

$ deno repl --eval 'import { assert } from "https://deno.land/std@0.104.0/testing/asserts.ts";'
Deno 1.13.0
exit using ctrl+d or close()
> assert(true);
undefined
> assert(false);
Uncaught AssertionError
    at assert (https://deno.land/std@0.104.0/testing/asserts.ts:217:11)
    at <anonymous>:2:1

8. navigator.hardwareConcurrency API のサポート

navigator.hardwareConcurrency API が実装されました。これは従来からあった Deno.systemCpuInfo() API を置き換える API で、CPU の論理コア数を取得することができます。

console.log(navigator.hardwareConcurrency);
// 筆者の環境では 8 が出力される

Deno.systemCpuInfo() は削除されたので、もし利用していた場合は navigator.hardwareConcurrency への置き換えが必要です。

9. V8 v9.3 にアップデート

Deno に組み込まれている JavaScript 実行エンジン V8 のバージョンが 9.3 に上がりました。これに伴い、2つの新しい JavaScript 言語機能が利用できるようになりました。

Error cause

Error オブジェクトが cause プロパティをもつようになりました。エラーをチェーンするのに利用できます。

const parentError = new Error("parent");
const error = new Error("parent", { cause: parentError });
console.log(error.cause === parentError);
// → true

Object.hasOwn

Object.prototype.hasOwnProperty.call のエイリアスとして Object.hasOwn が使えるようになりました。

Object.hasOwn({ prop: 42 }, "prop");
// → true

10. deno info で型宣言ファイルへの扱えるようになった

Deno は deno info というコマンドをもっています。このコマンドを使うと、指定したモジュールの依存関係を見ることができます。

今回のバージョンから、あるモジュールから参照されている型定義ファイルも依存関係に含めて出力することができるようになりました。Deno で型定義ファイルを参照する方法はいくつかあります。詳しくは こちらのマニュアル を参照してください。

11. Deno.writeFileAbortSignal のサポート

Deno.writeFileAbortSignal が利用できるようになりました。これにより、ファイルの書き込み途中にその処理の中断をすることができるようになります。ファイルの書き込みに予想外に時間がかかっている場合、ファイルサイズが大きすぎる場合などに途中で中断することができて便利です。

const aborter = new AbortController();
// 大きいデータ
const data = new UInt8Array(32 * 1024 * 1024);
// 大きいデータをファイルに書き込む
Deno.writeFile("./super_large_file.txt", { signal: aborter.signal })
  .then((data) => console.log("File write:", data.length))
  .catch((err) => console.error("File write failed:", err));
// 1000 ms 経っても書き込みが終わっていなければ、abort する
setTimeout(() => aborter.abort(), 1000);

12. Markdown ファイル内のコードに対しての型チェック

Markdown ファイル内の js ts jsx tsx コードに対して型チェックを行うことができるようになりました。deno test --doc コマンドを実行すると、拡張子 .md のファイルも読み込まれ、その中にある js ts jsx tsx のコードブロックがチェックされます。

Deno では標準ライブラリおよび公式マニュアルにてすでにこの deno test --doc が実行されていて、Markdown ファイル中のコードに型エラーがないかどうかが常にチェックされています。

試してみましょう。以下のような Markdown を用意します。

foo.md
```ts
const a = 42;
foo(a);

function foo(x: string) {
  console.log(x);
}
```

deno test --doc foo.md を実行すると、以下のように型エラーがある旨が表示されます。

Check file:///private/tmp/foo.md$1-9.ts
error: TS2345 [ERROR]: Argument of type 'number' is not assignable to parameter of type 'string'.
foo(a);
    ^
    at file:///private/tmp/foo.md$1-9.ts:2:5

13. きれいな環境の子プロセスを立ち上げることができるようになった

Deno.RunOptionsclearEnv というプロパティが追加されました。このプロパティを true にセットして Deno.run() に渡すと、生成される子プロセスに親プロセスからの環境変数が一切渡されないようになります。

clearEnv は将来機能変更される可能性があるため、利用するためには --unstable フラグをつける必要があります。

14. Deno.permissions API が URL を受け付けるようになった

パーミッション管理を実行時に行うための API として Deno.permissions が提供されています(詳細は マニュアル を参照)。

今まで、例えばファイルシステムに対しての読み込み権限を扱う際のパスとして string 型しか使えませんでしたが、URL 型を使うこともできるようになりました。

await Deno.permissions.query({
  name: "read",
  path: new URL(".", import.meta.url),
});
await Deno.permissions.query({
  name: "write",
  path: new URL(".", import.meta.url),
});
await Deno.permissions.query({
  name: "run",
  command: new URL(".", import.meta.url),
});

15. ネイティブプラグイン機構を置き換える FFI の実験的な実装

Deno は Deno.openPlugin() という API を提供していました。これは、Rust のダイナミックライブラリを読み込む API です。つまり Rust を使って Deno の機能を自由に拡張することができました。しかし、Rust の ABI が安定していないことに起因するさまざまな技術的問題に直面していました。

そこで、今回のバージョンから、より汎用的な FFI (Foreign Function Interface) API として Deno.dlopen() が追加され、Deno.openPlugin は廃止されました。新 API では、Rust 以外の言語で書かれたライブラリも呼び出すことができるようになります。

不安定機能のため、現時点では --unstable フラグが必要です。

C の関数を Deno から利用するサンプルを紹介します。まず、シンプルな足し算を行う add_numbers 関数を定義した add_numbers.c を作ります。

add_numbers.c
int add_numbers(int a, int b) {
  return a + b;
}

コンパイルして、共有ライブラリを作ります。

# unix
$ cc -c -o add_numbers.o add_numbers.c
$ cc -shared -Wl -o add_numbers.so add_numbers.o
# Windows
$ cl /LD add_numbers.c /link /EXPORT:add_numbers

Deno から共有ライブラリを読み込み、実行するコードは以下のようになります。

ffi.js
let libSuffix = "so";

if (Deno.build.os == "windows") {
  libSuffix = "dll";
}

const libName = `add_numbers.${libSuffix}`;
const dylib = Deno.dlopen(libName, {
  "add_numbers": { parameters: ["i32", "i32"], result: "i32" },
});

console.log(dylib.symbols.add_numbers(123, 456));

実行します。--allow-ffi --unstable が必要です。

$ deno run --allow-ffi --unstable ffi.js
579

16. [WebSocketStream] API の実験的実装

新たな WebSocket API として Streams API ベースのものを作るという作業が Chrome で進められています。この新しい API は WebSocketStream と呼ばれています。
なぜ Streams API ベースで作り直そうとしているかなどは web.dev の記事 にまとまっているので、ご参照ください。

どのように書き味が変わるのかを紹介します。まず、既存の WebSocket API を利用した場合のコード例です。

// 接続を初期化して、確立まで待機する
// 接続確立中にエラーが発生したら Promise が reject される
const ws = await new Promise((resolve, reject) => {
  const ws = new WebSocket("wss://example.com");
  ws.onerror = (e) => {
    reject(new Error(e.message));
  };
  ws.onopen = (e) => {
    resolve(ws);
  };
});

// `send` や `onmessage` エラーが起きても reject にはならないので、
// エラーイベントも listen する必要がある 
ws.onerror = (e) => {
  console.error("connection closed:", e.code, e.reason);
};

// リモートサーバーにテキストメッセージを送信する
// 送信中にエラーが起きたとしても、reject にはならない
ws.send("Hello server!");

// メッセージイベントを待ち受ける
ws.onmessage = (e) => {
  console.log("new message:", e.data);
};

続いて新しい API を利用したコードです。全体的に Promise、async/await に親和性のある API となっていることが読み取れると思います。

// 接続を初期化して、確立まで待機する
// 接続確立に失敗したら `wss.connection` が例外を投げる
const wss = new WebSocketStream("wss://example.com");
const { writable, readable } = await wss.connection;

// リモートサーバーにテキストメッセージを送信する
// もしエラーが起きたら `write.write` は reject される
const writer = writable.getWriter();
await writer.write("Hello server!");

// すべての受信メッセージをイテレートして、出力する
// もしエラーが起きたら非同期イテレータは reject になり、
// `for await` ループは打ち切られる
for await (const message of readable) {
  console.log("new message:", message);
}

新 API はまだ不安定なため、--unstable フラグが必要です。また、新 API に対するフィードバックが募集されています。

おわり

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

過去のリリース情報

1.12.0

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

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