Deno 1.17が来たぞ
2021年12月17日にDeno 1.17がリリースされました。
本記事では、上記のリリースノートの内容をざっと紹介していきます。
- Import assertions/JSON modulesに対応した
- Web Cryptography APIが拡充された
- --no-check=remoteフラグが追加された
- Deno.connectTls()にALPN対応が追加された
- unrefTimer/refTimerが追加された
- 各種APIが更新された
- TypeScript 4.5に対応した
- 標準ライブラリが更新された
Import assertions/JSON modulesに対応した
Import assertionsおよびJSON modulesがフルサポートされました。
これらは今年のはじめにV8にStage 3として導入されました。
リリースノートのサンプルがわかりやすいです。
JSONファイルを読み込む際、1.16.xまではfetch()
して.json()
するか、Deno.readTextFile()
してJSON.parse()
するかのどちらかでした。
これらは両方とも--allow-read
パーミッションが必要です。
// Get JSON data from remote server
const response = await fetch("https://example.com/data.json");
const jsonData = await response.json();
console.log(jsonData);
// Get JSON data from local file
const text = await Deno.readTextFile("./data.json");
const jsonData = JSON.parse(text);
console.log(jsonData);
1.17から、import jsonData from "..." assert { type: "json" }
という記法で直接JSONファイルを読み込めるようになります。
こちらは--allow-read
パーミッションが不要(dynamic importの場合は必要)となります。
// Get JSON data from remote server
import jsonData from "https://exmaple.com/data.json" assert { type: "json" };
console.log(jsonData);
// Get JSON data from local file
import jsonData from "./data.json" assert { type: "json" };
console.log(jsonData);
// You can also import JSON modules dynamically
const jsonData = await import("./data.json", { assert: { type: "json" } });
console.log(jsonData);
以下の記事により詳しい解説が載っています。
Web Cryptography APIが拡充された
過去のアップデートでも度々更新されてきましたが、Web Criyptography APIに再び機能が追加されました。
これにより、ブラウザとほぼ同機能になったということです。
-
crypto.subtle.importKey
でサポートするインポート形式の追加- SPKIフォーマットでのRSA鍵
- JWKフォーマットでのRSA鍵
- JWKフォーマットでのAES鍵
- PKCS#8フォーマットでのEC鍵
- SPKIフォーマットでのEC鍵
- JWKフォーマットでのEC鍵
-
crypto.subtle.exportKey
でサポートするエクスポート形式の追加- JWKフォーマットでのRSA鍵
- JWKフォーマットでのAES鍵
-
crypto.subtle.unwrapKey
でサポートするアンラッピングの追加- RSA-OAEP
- AES-GCM
まだ実装していない部分があるようですが、"likely be available in the next release"と書かれており、近いうちに完了する見込みとなっています。
今後のロードマップは以下のissueで議論されています。
--no-check=remoteフラグが追加された
起動オプションの--no-check
でremote
という引数を指定できるようになりました。
ローカルコードのみ型チェックを行い、リモートから読み込むモジュールは型チェックをスキップします。
> deno run --no-check=remote server.ts
将来的に、型チェックのデフォルトの挙動になる可能性があるとのことです。
Deno.connectTls()にALPN対応が追加された
Deno.connectTls()
にALPNの対応が追加されました。
以下はリリースノートのサンプルです。
サーバーがhttp/2をサポートしているかのネゴシエーションを行います。
const conn = await Deno.connectTls({
hostname: "example.com",
port: 443,
alpnProtocols: ["h2", "http/1.1"],
});
const { alpnProtocol } = await conn.handshake();
if (alpnProtocol !== null) {
console.log("Negotiated protocol:", alpnProtocol);
} else {
console.log("No protocol negotiated");
}
現時点では、実行に--unstable
フラグが必要です。
> deno run --allow-net --unstable deno117_alpn.ts
Negotiated protocol: h2
unrefTimer/refTimerが追加された
新たなunstable APIとして、Deno.unrefTimer(id: number)
とDeno.refTimer(id: number)
が追加されました。
タイマー(setTimeout
とsetInterval
)のイベントループ終了時のブロック周りの挙動を調整できます。
デフォルトでは、すべてのタイマーはクリアされない限りイベントループの終了をブロックします。
以下は、最初にhello world!
を表示し、2秒毎にhello after 2 sec
を表示するコードです。
setInterval(() => {
console.log("hello after 2 sec");
}, 2000);
console.log("hello world!");
タイマーのIDをDeno.unrefTimer
に指定することで、イベントループのブロックを回避できます。
以下のコードでは、hello world!
表示後に即終了します。
const timerId = setInterval(() => {
console.log("hello after 2 sec");
}, 2000);
Deno.unrefTimer(timerId);
console.log("hello world!");
このサンプルではconsole.log
を使っていたため一瞬で終了してしまいますが、この部分が一定の時間を要する処理で、その間setInterval
で外部からデータを取得したいとかログを出したいとかの用途に便利そうです。
各種APIが更新された
以下のAPIに更新が入りました。
AbortSignal
1.16でAbortSignal
に理由が指定できるようになったのですが、これが以下のAPIで適切に伝搬されるようになりました。
- ファイル入出力:
Deno.readFile()
Deno.readTextFile()
Deno.writeFile()
Deno.writeTextFile()
- Stream API関連:
ReadableStream
TransformStream
WritableStream
WebSocketStream
- fetch関連:
fetch
Request
Response
さらに、throwIfAborted()
メソッドが追加されました。
名前そのままですが、シグナルが既にAbortされている場合に例外を投げる機能を持ちます。
以下はリリースノートのサンプルです。
const controller = new AbortController();
const signal = controller.signal;
try {
signal.throwIfAborted();
} catch (err) {
unreachable(); // the abort signal is not yet aborted
}
controller.abort("Hello World");
try {
signal.throwIfAborted();
unreachable(); // the method throws this time
} catch (err) {
assertEquals(err, "Hello World");
}
Deno Language Server
DenoのLSPが更新され、モジュールの補完まわりの機能が強化されました。
また、キャッシュが無制限ではなく、レジストリから送られてくるヘッダを尊重するようになりました。
機能を適切に利用するには、以下のようにlspの設定ファイルのdeno.suggest.imports.hosts
に使用するレジストリを指定してください。
{
"deno.suggest.imports.hosts": {
"https://deno.land": true,
"https://cdn.nest.land": true,
"https://crux.land": true
}
}
加えて、vscode_denoにも更新が入り、有名なレジストリはデフォルトで有効になっているようです。
リリースノートには、esm.sh、Skypack、JSPMなどとの連携が強化されたとも記載があります。
Deno専用に限らず、Nodeの資産を利用する方向でも開発環境が整ってきています。
deno test
Deno.test()
APIに更新が入りました。
これまで、Deno.test()
の書き方には以下の2通りがありました。
import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";
// "name", function
Deno.test("My test description", (): void => {
assertEquals("hello", "hello");
});
// { "name", fn(), options }
Deno.test({
name: "example test",
ignore: Deno.build.os == "windows",
fn(): void {
assertEquals("world", "world");
},
});
今回のリリースで、次の4通りが追加されました。
import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";
// namedFunction
Deno.test(function myTestName(): void {
assertEquals("hello", "hello");
});
// "name", { options }, function
Deno.test("My test description", { permissions: { read: true } }, (): void => {
assertEquals("hello", "hello");
});
// { "name", options }, function
Deno.test(
{ name: "My test description", permissions: { read: true } },
(): void => {
assertEquals("hello", "hello");
},
);
// { options }, namedFunction
Deno.test({ permissions: { read: true } }, function myTestName(): void {
assertEquals("hello", "hello");
});
ユーザーが必要な項目だけ書けるようにバリエーションが増えた形ですが、自由度が高まったと見るか混沌に近づいたと見るかは人それぞれでしょうか。
また、nested testの実行結果に、ネストされたステップの成功/失敗などの情報が含まれるようになりました。
Deno.test("nested failure", async (t) => {
const success = await t.step("step 1", async (t) => {
let success = await t.step("inner 1", () => {
throw new Error("Failed.");
});
if (success) throw new Error("Expected failure");
success = await t.step("inner 2", () => {});
if (!success) throw new Error("Expected success");
});
if (success) throw new Error("Expected failure");
});
step
の実行には--unstable
フラグが必要です。
これは1.18で安定化が予定されています。
> deno test --unstable deno117_test.ts
running 1 test from file:///Users/kawarimidoll/deno_sample/deno117_test.ts
test nested failure ...
test step 1 ...
test inner 1 ... FAILED (5ms)
Error: Failed.
at file:///Users/kawarimidoll/deno_sample/deno117_test.ts:5:13
at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
at exitSanitizer (deno:runtime/js/40_testing.js:170:15)
at TestContext.step (deno:runtime/js/40_testing.js:800:19)
at file:///Users/kawarimidoll/deno_sample/deno117_test.ts:4:27
at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
test inner 2 ... ok (3ms)
FAILED (10ms)
FAILED (13ms)
failures:
nested failure
Error: 1 test step failed.
at runTest (deno:runtime/js/40_testing.js:432:11)
at async Object.runTests (deno:runtime/js/40_testing.js:541:22)
failures:
nested failure
test result: FAILED. 0 passed (1 step); 1 failed (2 steps); 0 ignored; 0 measured; 0 filtered out (24ms)
error: Test failed
上記のtest result
の行の(1 step)
などがそれです。
筆者の手許で試した限りでは、1.16.2の時点で入っていたものと思われます。
--watch フラグ
起動時の--watch
フラグに更新が入りました。
以下のように引数を渡すことで、module graphにないファイル(要するに実行ファイル内でimport
されていないファイル)の更新を監視できるようになりました。
> deno run --watch=data/external.txt script.ts
また、リロードしたときにコンソールをクリアするようになりました。
自分が以下のプロジェクトで作ったものがほぼ全て公式から提供されてしまいました。
使いやすくなるのは嬉しいことだけど一抹の寂しさがある…。
REPL
deno
またはdeno repl
で起動するREPLが更新されました。
モジュール名の補完
以下のように入力して<TAB>
キーを押すとパスが補完されるようになりました。
> import xxx from "https://deno.land/x
Node.jsモードの対応
deno repl --compat --unstable
で起動することで、REPLでもNode.jsモードが使えるようになりました。
--unsafely-ignore-certificate-errorsフラグの追加
--unsafely-ignore-certificate-errors
フラグを付けて起動することで、TLS証明書の検証を無効にした状態でREPLを実行できるようになりました。
エラーを無視してしまうと危険なので使用には注意が必要とのことです。
FFI API
1.15当時に追加されていたFFI APIですが、これに関連してDeno.UnsafePointer
とDeno.UnsafePointerView
APIが追加されました。
筆者のFFIの知見が足りないのでここについて詳しいことが書けません、申し訳ないです…。
TypeScript 4.5に対応した
最新のTypeScript 4.5に対応したことで、新しいimport
記法が使えるようになりました。
関数やクラス等と型を一度にimport
に書けるようになります。
// old
import { A } from "https://example.com/mod.ts";
import type { B } from "https://example.com/mod.ts";
// new
import { A, type B } from "https://example.com/mod.ts";
標準ライブラリが更新された
標準ライブラリのdeno_stdも0.118.0に更新されました。
破壊的変更
以下のBREAKING CHANGEが入っています。
-
ws
モジュールを削除- 代わりに
Deno.upgradeWebSocket()
を使用してください
- 代わりに
-
testing/asserts.ts
のassertThrowsAsync
を削除- 代わりに同モジュールの
assertRejects
を使用してください
- 代わりに同モジュールの
-
http/server_legacy.ts
を削除- 代わりに
http/server.ts
を使用してください
- 代わりに
-
fs/mod.ts
からcopy
を削除- 代わりに
fs/copy.ts
から直接インポートしてください
- 代わりに
-
signals
からonSignal
を削除- 代わりに
Deno.addSignalListener()
を使用してください
- 代わりに
-
collections
からfindLast
とfindLastIndex
を削除-
Array
とTypedArray
自体にfindLast
とfindLastIndex
が定義されているのでそちらを使用してください
-
-
http
サーバが文字列のアドレスを受け付けなくなり、{ host: string, port: number }
インターフェースを利用するよう変更
すでにdeprecatedになっていたものが削除されたというのが多いですが、結構BREAKINGなので、各モジュールを使用している場合は確認したほうが良さそうです。
HTTPサーバーのオプションの追加
これまでstd/server
のserve
は、ハンドラの生成するエラーを無視してしまっていましたが、0.118からはエラーをconsole.error
で出力するようになりました。
この挙動は、Server
クラスにonError
を渡すことでカスタマイズできます。
import { Server } from "https://deno.land/std@0.118.0/http/server.ts";
const port = 4505;
const handler = (request: Request) => {
const body = `Your user-agent is:\n\n${
request.headers.get(
"user-agent",
) ?? "Unknown"
}`;
return new Response(body, { status: 200 });
};
const onError = (_error: unknown) => {
return new Response("custom error page", { status: 500 });
};
const server = new Server({ port, handler, onError });
std/collectionsへの関数の追加
aggregateGroups
関数が追加されました。
値が配列であるようなオブジェクトとアグリゲータ関数を引数に取り、値にアグリゲータ関数を適用した結果のオブジェクトを返します。
日本語だと意味がわかりませんね。コードをご覧ください。
[Array.prototype.reduce()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
)をオブジェクトのvalueに対して実行しているようなイメージでしょうか。
import { aggregateGroups } from "https://deno.land/std@0.118.0/collections/mod.ts";
const foodProperties = {
Curry: ["spicy", "vegan"],
Omelette: ["creamy", "vegetarian"],
};
const descriptions = aggregateGroups(
foodProperties,
(current, key, first, acc) =>
first ? `${key} is ${current}` : `${acc} and ${current}`
);
console.log(descriptions);
> deno run aggregate_groups.ts
{ "Curry": "Curry is spicy and vegan", "Omelette": "Omelette is creamy and vegetarian" }
関係ないのですが、リリースノートにstd/collections is a playground for us to experiment with more advanced functions for iterating over arrays and iterables.
と記載があり、playgroundだったんか…と思いました。確かに他のモジュールと比べて実験的な関数も多いように感じられますね。
個人的には普通に便利で面白いと感じています。
fmt/bytesの追加
fmt/bytes
モジュールが追加されました。バイト形式を人間が読みやすい形式にフォーマットするための関数を提供します。
import { prettyBytes } from "https://deno.land/std@0.118.0/fmt/bytes.ts";
import { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";
assertEquals(prettyBytes(1024), "1.02 kB");
assertEquals(prettyBytes(1024, { binary: true }), "1 kiB");
npmのpretty-bytesモジュールを移植したものとのことです。
おわりに
個人的には今回のリリースでは--watch
機能の更新が印象深いです。
記事中でも少し触れましたが、自作のツールでこねくり回してなんとか実装していたのですが、公式から提供されたことで、御役御免となりました。
嬉しいような寂しいような。
また、秋ごろに少し話題の出ていたDeno 2は、一旦保留になったようです。
ブログで触れられていない詳細な更新内容に関しては、GitHubのリリースページをご覧ください。
Discussion