Deno 1.13.0 がリリースされたので新機能や変更点の紹介
Copyright (c) 2018-2021 the Deno authors. MIT License.
日本時間の昨日(2021 年 8 月 11 日)に Deno の v1.13.0 がリリースされました。
詳細なリリース内容は上記のリリースノートにまとまっていますが、ざっと紹介していきたいと思います。
- ネイティブ HTTP サーバー実装の安定化
-
self.structuredClone()
のサポート - TLSのためのシステム証明書ストアが利用可能になった
- テストのためTLS検証を無効にできるようになった
- Web Crypto API のアップデート
- Deno Language Server と VS Code 拡張機能のアップデート
- REPL の改良
-
navigator.hardwareConcurrency
API のサポート - V8 v9.3 にアップデート
-
deno info
で型宣言ファイルへの扱えるようになった -
Deno.writeFile
でAbortSignal
のサポート - Markdown ファイル内のコードに対しての型チェック
- きれいな環境の子プロセスを立ち上げることができるようになった
-
Deno.permissions
API がURL
を受け付けるようになった - ネイティブプラグイン機構を置き換える FFI の実験的な実装
- [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 のマニュアルは ここ にあります。
self.structuredClone()
のサポート
2. 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 の対応がさらに進みました。
-
crypto.subtle.importKey()
とcrypto.subtle.exportKey()
で生 HMAC キーを扱えるようになりました -
crypto.subtle.verify()
で HMAC キーから生成された署名を検証できるようになりました
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
navigator.hardwareConcurrency
API のサポート
8. 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
deno info
で型宣言ファイルへの扱えるようになった
10. Deno は deno info
というコマンドをもっています。このコマンドを使うと、指定したモジュールの依存関係を見ることができます。
今回のバージョンから、あるモジュールから参照されている型定義ファイルも依存関係に含めて出力することができるようになりました。Deno で型定義ファイルを参照する方法はいくつかあります。詳しくは こちらのマニュアル を参照してください。
Deno.writeFile
で AbortSignal
のサポート
11. Deno.writeFile
で AbortSignal
が利用できるようになりました。これにより、ファイルの書き込み途中にその処理の中断をすることができるようになります。ファイルの書き込みに予想外に時間がかかっている場合、ファイルサイズが大きすぎる場合などに途中で中断することができて便利です。
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 を用意します。
```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.RunOptions
に clearEnv
というプロパティが追加されました。このプロパティを true
にセットして Deno.run()
に渡すと、生成される子プロセスに親プロセスからの環境変数が一切渡されないようになります。
clearEnv
は将来機能変更される可能性があるため、利用するためには --unstable
フラグをつける必要があります。
Deno.permissions
API が URL
を受け付けるようになった
14. パーミッション管理を実行時に行うための 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
を作ります。
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 から共有ライブラリを読み込み、実行するコードは以下のようになります。
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
1.11.0
1.10.1
1.9.0
1.8.0
1.7.0
1.6.0
Discussion