皆さんこんにちは。自宅のダイナゼノンが、カイゼルグリッドナイトになりました。おっくんです。
去る 2021 年 8 月 10 日に Deno 1.13 がリリースされました。 今回も、リリースノートを参考に 変更事項の気になるところを紹介したいと思います。
実行環境
- Docker イメージ denoland/deno:centos(確認時点では Deno 1.13.0 でした)
Deno 1.13
Deno 1.13 での変更事項をDeno 1.13 リリースノートを元に確認します。
ネイティブ HTTP サーバー API が安定化しました。
Deno 1.9 より登場した ネイティブ HTTP サーバー API が安定化し、利用に当たって --unstable
の付与が不要になりました。
Rust の高速な HTTP サーバー hyper Webサーバーを JavaScript API として利用できます。 この数週間、https://deno.land への数百万件に及ぶリクエストを新しい API で対応したことからも、十分安定した API だと確信しているそうです。
https://deno.land/に記載されていたサーバーのサンプルコードも、ネイティブ HTTP サーバー API を利用した、以下のコードに切り替わっていました。
const listener = Deno.listen({ port: 8000 }); console.log("http://localhost:8000/"); for await (const conn of listener) { (async () => { const requests = Deno.serveHttp(conn); for await (const { respondWith } of requests) { respondWith(new Response("Hello world")); } })(); }
これまで見られていた以下のような std/http を使用した サーバーは、ネイティブ HTTP サーバー API への切り替えが推奨されています。
// 今後使えなくなる std/http を使った実装 import { serve } from "https://deno.land/std@0.91.0/http/server.ts"; const s = serve({ port: 8000 }); console.log("http://localhost:8000/"); for await (const req of s) { req.respond({ body: "Hello World\n" }); }
std/http は今後いくつかのバージョンでは、利用を継続できますが、もうすぐ削除になるそうです。 削除にあたり、次のようにサポートされます。
- 移行するための情報は、近日マニュアルに記載される予定である
- oakの最新版を使っていれば、Deno 1.13 環境で動かすとネイティブ API にシームレスに移行できる
denoの第3クォーターのロードマップには、std/http のオーバーホールが予定されており、あまり遠くない未来に実施されるのかもしれません。
構造化クローンアルゴリズム を使用する関数 self.structuredClone() が追加されました
新しく、self.structuredClone() が、追加されます。 この、self.structuredClone() は、これまで Web Worker とのメッセージのやり取りなどで使用されていた機能になります。
self.structuredClone() を使うと、値(オブジェクト)のディープクローンができます。 構造化クローンアルゴリズムについては、MDN に、The structured clone algorithmという記事があるので、こちらが参考になります。
self.structuredClone() を使ってみます。
[clone.ts]
import { assert } from "https://deno.land/std@0.104.0/testing/asserts.ts"; // self.structuredClone() 動作確認 const foo = { bar: "baz" }, foo = { bar: "baz" }; foo.foo = foo; const clone = structuredClone(foo); // self.structuredClone は self 名前空間に属した関数なので self は省略できる assert(foo1.foo === foo); assert(clone !== foo); assert(clone.bar === "baz"); assert(clone.foo1 === clone); // <= 循環している構造としてコピーできている
deno run --no-check clone.ts
で実行します。
cloneDeep でも同様の self.structuredClone() と同様の動作を確認できました。 外部のライブラリ依存なくディープクローンができるのは非常に助かります。
この self.structuredClone() は、 Chrome や firefox にも structuredClone() が追加される可能性があるそうです。
環境変数で、TLS に使用する認証局をシステム CA ストアに変更できるようになった
Deno 1.13 から、Deno で使用できる環境変数に、DENO_TLS_CA_STORE
が追加になります。
DENO_TLS_CA_STORE
を使うことで、Deno が TLS に対して、信頼する認証局を変更することができます。
設定可能な値は、mozilla
と system
で、デフォルト値は mozilla
です。
DENO_TLS_CA_STORE=system deno run hogehoge.ts
のように使用することで、各種 OS のシステムルート CA ストア(windows なら、システム証明書ストア、macOS なら、キーチェイン)が使えるようになります。
この環境変数を使用すると、Deno 組み込みの CA ストアは無効になります。
REPL が改善されました
Deno の REPL に 2 つの大きな改善が入ります。
export
を無視します。
これまで export
を REPL で使うと、次のように シンタックスエラーになっていました。
$ deno Deno 1.12.0 exit using ctrl+d or close() > export const val = 1; Uncaught SyntaxError: Unexpected token 'export'
Deno 1.13 より、REPL での export
の使用は、無視されるのでエラーにならなくなります。
export
が書かれているモジュールをコピーアンドペーストして確認するときなど、手間が減ります。
--eval フラグが追加されました
--eval を使うことで、REPL 起動時に、与えたスクリプトを実行してから、対話的操作に移ります。
注意する点は、deno --eval ~~
でなく deno repl --eval ~~
のように repl
が必要です。
次のように使えます。
# deno repl --eval 'let val = 1; val = 2;'
Deno 1.13.0
exit using ctrl+d or close()
> val
2
>
リリースノートでは、--eval
フラグで外部モジュールのインポートを紹介しています。
REPL で確認をしていく際に、必須のモジュールなどが固まっているようであれば、シェルスクリプトとして次のように書いてしまうのがいいと思います。
#!/bin/bash # repl.sh として保存 deno repl --eval 'let val = 1; val = 2;'
これで、./repl.sh
とするだけで、val が定義された状態で起動できます。
REPL の起動都度、外部モジュールのインポートを毎回履歴から実行するという手間がなくなるので、とてもありがたい更新でした。
navigator.hardwareConcurrency API がサポートされるようになります
読み取り専用の navigator.hardwareConcurrency プロパティは、ユーザーエージェントが使用可能な、論理プロセッサの個数を返します。 詳細は、MDN にあるので、こちらを参照されるのがいいと思います。
MDN - Navigator.hardwareConcurrency
> console.log(navigator.hardwareConcurrency);
6 // Windows 上の Docker コンテナの場合 macOS では 4 でした
この API により得られる値を元に、Web worker の立ち上げ個数を制御するといった用途に使うそうです。
v8 バージョンが 9.3 になります。
Deno 1.13 では、Deno 1.12 に引き続き v8 が更新されます。 更新されたことで、新しい JavaScript の機能が使えるようになります。
リリースノートでのサンプルコードから紹介します。
Error cause
Error オブジェクトが cause プロパティを持つようになります。
const parentError = new Error("parent"); const error = new Error("parent", { cause: parentError }); console.log(error.cause === parentError); // → true
こちら、Chrome などブラウザでは、未対応でした。 MDN - Error
Object.hasOwn
オブジェクトが指定のキーのプロパティを持つか問い合わせる Object.prototype.hasOwnProperty.call
のエイリアス Object.hasOwn
が導入されます。
> const obj = {key:1}; undefined > Object.prototype.hasOwnProperty.call(obj,"key") true > Object.hasOwn(obj,"key") // <= true
こちらも、Chrome などブラウザでは、未対応でした。 MDN - Object.hasOwn()
ここで、v8 のバージョン最新版を確認してみると現在 9.4 系となっているので、Deno も併せてアップデートがそのうちあるのではと思います。 現在のプルリクには該当するようなものはありませんでした。
https://github.com/denoland/deno/pulls?page=2&q=is%3Apr+is%3Aopen
Deno info
deno info hoge
とすると、hoge モジュールの依存情報が参照できるサブコマンドがあります。
Deno 1.13 から、/// <reference lib="...">
や、// @deno-types
ディレクティブ、X-TypeScript-Types ヘッダーにより指定される型宣言が、ツリービューに表示されるようになるそうです。
/// <reference lib="...">
や、// @deno-types
ディレクティブ でのツリービューの表示内容を Deno 1.13.0 と Deno 1.12.0 の間で確認してみたのですが、ちょっと違いが掴めなかったので、これは別途改めて確認したいと思います。
Deno.writeFile が、AbortSignal をサポート
Deno 1.12 では、Deno.readFile ファイルの AbortSignal 対応がリリースされていました。 引き続き、Deno 1.13 では、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)); setTimeout(() => aborter.abort(), 1000);
マークダウンドキュメント内の、型チェックができるようになりました。
Deno 1.10 にて、ソースコードのコメントに書かれたサンプルコードの型チェックがリリースされていました。
Deno1.13 では、マークダウンのコードブロックに書かれた JavaScript jsx TypeScript tsx をチェックできるようになります。
やってみます。
読み込み対象のモジュールを作成。
[mod.ts]
// mod.ts export const func1 = (a: number, b: number): number => { return a + b; };
使用例を書いたマークダウンを次のように作成します。
[doc.md]
引数の型を誤った使い方
```ts
import { func1 } from "./mod.ts";
console.log(func1("1", "2"));
```
$ deno test --doc doc.md test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (14ms) error: An unsupported media type was attempted to be imported as a module. Specifier: file:///usr/src/app/doc.md MediaType: Unknown
上記の通り、サポートしていないメディアタイプだというエラーになります。 記述の仕方が問題となっていないことから、現在利用できない状態のようです。
環境変数を引き継がずに、サブプロセスを起動できるようになります。
新たな --unstable フラグを要求する機能として、Deno.run
のオプション Deno.RunOptions
インターフェースに、clearEnv
が追加されました。
Deno 実行時の環境変数が、Deno.run で起動したサブプロセスに引き継がれなくなります。 試してみます。
Deno.run で起動されるシェルクリプトは以下の通りです。
[show_env.sh]
#!/usr/bin/sh # 環境変数を表示するだけ echo sh:$AAA
このシェルを呼び出す スクリプト env.ts を次のようにします。
まず、前提として clearEnv
オプションを使用しないものを確認します。
[env.ts]
// Denoとして読み込んだ環境変数を表示 console.log(`Deno:${Deno.env.get("AAA")}`); const result = Deno.run({ cwd: "./", cmd: ["sh", "show_env.sh"], stdout: "piped", }); await result.status(); // Deno.run の実行結果を表示 console.log(new TextDecoder().decode(await result.output()));
実行してみます。
$ AAA=aaaaa deno run --allow-run --allow-env env.ts Check file:///usr/src/app/env.ts Deno:aaaaa sh:aaaaa
実行時に渡した AAA=aaaaa
が Deno.run で機能したサブプロセスにも引き継がれています。
この引継ぎをされないようにできるのが、clearEnv
オプションです。
env_clean.ts を改修します。
[env_clean.ts]
console.log(`Deno:${Deno.env.get("AAA")}`); const sub = Deno.run({ cwd: "./", cmd: ["sh", "show_env.sh"], stdout: "piped", clearEnv: true, // <= clearEnv: true を記述 }); console.log(new TextDecoder().decode(await sub.output()));
実行すると次のようになります。
$ AAA=aaaaa deno run --unstable --allow-run --allow-env env_clean.ts Check file:///usr/src/app/env.ts Deno:aaaaa sh:
サブプロセスでの環境変数が参照できなくなりました。
ここで、気になるところとしては、clearEnv: true
を設定しなかったとき、他にも引き継がれている環境変数があるのか?です。
show_env.sh を修正してシェルが読み込んでいる環境変数をすべて表示してみます。
[show_env.sh(修正)]
#!/usr/bin/sh # 環境変数をすべて表示 env
実行します。
# clearEnv: true 未設定 $ AAA=aaaaa deno run --allow-run --allow-env env.ts Check file:///usr/src/app/env.ts LANG=en_US.UTF-8 HOSTNAME=3117b748d4e9 DENO_VERSION=1.13.0 DENO_DIR=/deno-dir/ DENO_INSTALL_ROOT=/usr/local PWD=/usr/src/app HOME=/root AAA=aaaaa TERM=xterm SHLVL=2 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin LESSOPEN=||/usr/bin/lesspipe.sh %s _=/usr/bin/env # clearEnv: true 設定済 $ AAA=aaaaa deno run --unstable --allow-run --allow-env env_clean.ts Check file:///usr/src/app/env_clean.ts Deno:aaaaa sh: PWD=/usr/src/app SHLVL=1 _=/usr/bin/env # OSに定義されている環境変数 $ env LANG=en_US.UTF-8 HOSTNAME=3117b748d4e9 DENO_VERSION=1.13.0 DENO_DIR=/deno-dir/ DENO_INSTALL_ROOT=/usr/local PWD=/usr/src/app HOME=/root TERM=xterm SHLVL=1 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin LESSOPEN=||/usr/bin/lesspipe.sh %s _=/usr/bin/env
clearEnv: true
を設定した時には、多数の環境変数がサブプロセスに引き継がれていることがわかります。
Deno と直接関係しないような定義済の環境変数も引継ぎされないものもあり、用途によってはむしろ邪魔になってしまうケースがあるかもしれません。
Permissions API に URL を設定できるようになりました。
Deno.permissions API で対象を指定する時、これまでは read write run に、文字列でパスを指定してきました。 こちらが URL オブジェクトも受け付けるようになります。
memo というファイルが現在のディレクトリにあるものとして、ファイルの読み込みを試してみます。
[permissions_url.ts]
// URLで読み込み先ファイルを準備 const url = new URL("memo", import.meta.url); const permissionsQuery = async () => { return await Deno.permissions.query({ name: "read", path: url, }); }; // 現在のパーミッション アクセス権無 console.log(await permissionsQuery()); // 対話的なアクセス権取得処理 await Deno.permissions.request({ name: "read", path: url, }); // 現在のパーミッション アクセス権有 console.log(await permissionsQuery()); console.log(Deno.readTextFileSync(url)); // アクセス権取り消し await Deno.permissions.revoke({ name: "read", path: url, }); // 現在のパーミッション アクセス権無 console.log(await permissionsQuery());
実行すると次のようになります。
$ deno run permissions.ts Check file:///usr/src/app/permissions.ts PermissionStatus { state: "prompt", onchange: null } ⚠️ ️Deno requests read access to "/usr/src/app/memo". Allow? [y/n (y = yes allow, n = no deny)] y PermissionStatus { state: "granted", onchange: null } hogehoge PermissionStatus { state: "prompt", onchange: null }
リリースノートに示されているDeno.permissions.query
だけでなく、Deno.permissions.request
と Deno.permissions.revoke
でも URL オブジェクトで受け付けることができています。
サンプルコードで示しているとおり、Deno.readTextFileSync
が URL オブジェクトで受け付けられることからも、部分的に文字列で書く必要が無くなり、ソースコードの見通しがよくなると思います。
ネイティブプラグインが削除され、FFI が追加されます
deno の第 3 クォーターのロードマップにて、ネイティブプラグインの削除が予告されていました。 代替として、新たに用意された FFI API に置き換えられます。
現時点では、実験的機能として実行時には、--unstable
の付与が必要です。
動作確認に使用している docker イメージ denoland/deno:centos-1.13.0 で実行できるまでの順を追って記載します。
gcc のインストール。
yum install -y gcc
ソースコードの作成
// add_numbers.c int add_numbers(int a, int b) { return a + b; }
共有ライブラリとして、コンパイルします。
cc -c -o add_numbers.o add_numbers.c cc -shared -o add_numbers.so add_numbers.o
リリースノートに記載された -Wl オプションは、入れていません。今回の共有ライブラリの内容では、リンカオプションの設定は不要だったためです。
作成した add_numbers.so を読み込む、ffi.js を作成します。
[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" }, });
実行するために、--allow-ffi
を付与することで ffi が使用できます。
$ deno run --allow-ffi --unstable ffi.js 579
libffi-devel などの環境構築を要するライブラリを導入せずとも使えるのが、とてもいいですね。
Deno.dlopen
の第 2 引数に 共有ライブラリに記述されている関数の構造を記述する必要があります。
Rust の wasm-bindgenのような、共有ライブラリに合わせて JavaScript のコードや Typescript 型定義を生成をしてくれるツールが欲しくなりますね。
WebSocketStreamAPI が追加
Deno 1.12 リリースノートでは、Deno のネイティブウェブサーバーが WebSocket をサポートしました。 その際に、既存の WebSocket オブジェクトは、WebSocketStream が安定して使えるようになれば、切り替える予定あると紹介されていました。
この WebSocketStream が、実験的な API として Deno に導入されます。 リリースノートに示されているコードを紹介します。
// 新しい WebSocketStream API const wss = new WebSocketStream("wss://example.com"); const { writable, readable } = await wss.connection; const writer = writable.getWriter(); await writer.write("Hello server!"); for await (const message of readable) { console.log("new message:", message); }
// WebSocket API 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); }; }); ws.onerror = (e) => { console.error("connection closed:", e.code, e.reason); }; ws.send("Hello server!"); ws.onmessage = (e) => { console.log("new message:", e.data); };
WebSocketStream は、async/await の API に変わり、かなり見通しがよくなったように感じます。
実験的な API なので、実行には、--unstable
が必要です。
その他
- TLS 検証を無効にするフラグ --unsafely-ignore-certificate-errors を追加
追加はされたものの、通信の機密性が損なわれるので、基本的に使用が推奨されないものになります。 - Deno 言語サーバーと、VSCode 向け拡張機能のアップデート
- WebCrypto API の更新
HMAC 形式のキーのインポート、エクスポートに新たに対応し、既存の
crypto.subtle.verify()
関数も HMAC 形式に対応しました。- crypto.subtle.importKey():追加
- crypto.subtle.exportKey():追加
- crypto.subtle.verify():HMAC 形式への対応追加
Deno 1.13 のリリースノートを追ってきました。 FFI API がエポックメイキングなリリースと言えるのではないかと思います。 特に特別な環境構築なく、作成した共有ライブラリから関数の呼び出しができたのは非常に快適でした。
次の リリースは 9 月 14 日予定です。またそのころ Deno 1.14 でお会いしましょう。
P.S.
採用情報
■募集職種
yumenosora.co.jp
カジュアル面談も随時開催中です
■お申し込みはこちら!
yumenosora.connpass.com
■ToraLab.fmスタートしました!
メンバーによるPodcastを配信中!
是非スキマ時間に聞いて頂けると嬉しいです。
anchor.fm
■Twitterもフォローしてくださいね!
ツイッターでも随時情報発信をしています
twitter.com