Deno v1.20がリリースされました。

この記事では新しく追加された機能などについて紹介します。

(破壊的変更) Deno.testのpermissionsオプションの挙動が変更

⚠️ この変更はv1.20.1時点ではまだ反映されていません!

現在、正式な修正用のPRが作成されており、おそらくv1.20.2で反映されるはずです

Deno.testWorkerなどのAPIはpermissionsオプションにより実行時のパーミッションをカスタマイズできます:

Deno.test({
  name: "permissions_test",
  permissions: { read: true },
  fn: async () => {
    const content = await Deno.readTextFile("./data.txt");
    await Deno.writeTextFile("./data.txt", processContent(content));
  },
});

上記のテストコードはDeno.readTextFileDeno.writeTextFileを実行しており、正しく実行するためには--allow-read--allow-writeの両方の権限が必要です。

このテストケースでは、下記宣言により--allow-readを明示的に指定しています。

  permissions: { read: true },

ここではwrite: trueが指定されていないため、このテストケースは権限エラーにより失敗するというのが直感的な挙動なのではないかと思います。

しかし、Deno v1.19時点では上記のテストコードは成功してしまいます。

  permissions: { read: true },

実は、Deno v1.19において、この指定は下記宣言と同義になります:

  // `read`以外はすべてCLIオプションで指定された権限(--allow-writeなど)が継承される
  permissions: {
    read: true,
    env: "inherit",
    ffi: "inherit",
    hrtime: "inherit",
    net: "inherit",
    run: "inherit",
    write: "inherit",
  },

この挙動は直感的ではないということで、Deno v1.20にて修正されました。

具体的には、permissions: { read: true }の指定は、下記宣言と同義になります:

  permissions: {
    read: true,
    env: "none",
    ffi: "none",
    hrtime: "none",
    net: "none",
    run: "none",
    write: "none",
  },

この変更により、先程紹介したテストコードは、直感通り権限エラーによって失敗するようになります。

deno taskコマンドが実装

⚠️ このコマンドはまだunstableという扱いであり、今後まだ変更が入る可能性が高いためご注意!

Deno本体にnpm-scripts相当の機能が実装されました。

使用方法としては、まずdeno.json(c)"tasks"フィールドでスクリプトを定義します。

{
  "tasks": {
    "start": "deno run --allow-net mod.ts"
  }
}

このように定義したスクリプトはdeno task <タスク名>で実行できます:

$ deno task start

また、引数なしでdeno taskを実行するとdeno.json(c)で定義されているタスク一覧を表示することができます:

$ deno task

一見、npm-scriptsに似ていますが、下記のような違いがあります:

  • cross-envライクな環境変数指定のサポート
  • cpmvなどのコマンドを組み込みでサポート (定義されているコマンドについてはマニュアルを参照ください)

このように、deno taskでは各プラットフォームごとの差分をうまく吸収することが意識されています。(これはdeno_task_shellというRust crateによって実現されています)

deno benchコマンドが実装

Deno本体にベンチマークを取る機能が実装されました。

まず、Denoネームスペースに下記APIが追加されています:

Deno.bench({
  // ベンチマークの実行回数 (デフォルトは`1000`)
  n: 1000,

  // ウォームアップの実行回数 (デフォルトは`1000`)
  // JITによる最適化を目的として、ベンチマーク開始前に、ここで指定された回数だけ`fn`が繰り返し実行されます (このウォームアップ処理は計測結果には影響しません)
  warmup: 1000,

  // ベンチマークコード
  // `n`+`warmup`の回数だけこの関数が実行されます
  fn: () => {
    doSomeHeavyComputation();
  },
});

このAPIを使ってベンチマークコードを記述します。

例えば、下記のように利用します。

function sum(...numbers: Array<number>): number {
  return numbers.reduce((a, b) => a + b, 0);
}

Deno.bench({
  name: "sum",
  fn: () => {
    sum(1, 2, 3, 4, 5);
  },
});

⚠️ ベンチマークコードはdeno testと同様に、bench.ts*_bench.tsなどの名前のファイルで記述する必要があります。

そして、deno benchコマンドを実行すると、定義したベンチマークコードを実行することができます。

$ deno bench --unstable
running 1 bench from file:///home/uki00a/ghq/github.com/uki00a/deno-sample/bench.ts
bench sum ... 1000 iterations 842 ns/iter (682..59,111 ns/iter) ok
(7ms)

bench result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (17ms)

こちらのコマンドもまだunstableという扱いであり、今後変更が入る可能性があるためご注意を!

deno test--no-promptの挙動がデフォルト化

Deno v1.19--promptオプションの挙動がデフォルト化されました

ただし、この振る舞いはdeno testにおいては適切ではないということで、deno testの実行時は--no-promptオプションの挙動がデフォルト化されました。

deno test --trace-ops

deno testコマンドはリソースリークを検知する機能を備えています。

Deno v1.19でこのリソースリーク発生時のトレース情報が大幅に改善されました。

しかしこの改善されたトレース情報の出力にはパフォーマンスに影響があったようで、今回のv1.20でデフォルトで無効化されました。

そして、--trace-opsという新しいオプションが導入され、改善されたトレース出力を任意で有効化できるようになりました。

$ deno test --trace-ops test.ts
running 1 test from file:///home/uki00a/ghq/github.com/uki00a/deno-sample/test.ts
test trace_ops ... FAILED (6ms)

failures:

trace_ops
Test case is leaking async ops.

- 1 async operation to sleep for a duration was started in this te
st, but never completed. This is often caused by not cancelling a
`setTimeout` or `setInterval` call. The operation was started here
:
    at Object.opAsync (deno:core/01_core.js:161:42)
    at runAfterTimeout (deno:ext/web/02_timers.js:234:31)
    at initializeTimer (deno:ext/web/02_timers.js:200:5)
    at setTimeout (deno:ext/web/02_timers.js:337:12)
    at file:///home/uki00a/ghq/github.com/uki00a/deno-sample/test.ts:2:3
    at testStepSanitizer (deno:runtime/js/40_testing.js:441:13)
    at asyncOpSanitizer (deno:runtime/js/40_testing.js:145:15)
    at resourceSanitizer (deno:runtime/js/40_testing.js:367:13)
    at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js
:424:15)
    at runTest (deno:runtime/js/40_testing.js:784:18)

HTTPレスポンスの自動的な圧縮がサポート

Denoの組み込みHTTPサーバでレスポンスボディの自動的な圧縮がサポートされました。

まず、Deno.serveHttp()std/httpを使用してHTTPサーバを起動します。

import { serve } from "https://deno.land/std@0.130.0/http/mod.ts";

serve(async () => {
  const json = await Deno.readTextFile("./data.json");
  return new Response(json);
});

そして、Accept-Encodingヘッダを付与してサーバにリクエストを送信します。

すると、下記のようにBrotliによって圧縮された状態でレスポンスが返却されます。

$ curl -I -H "Accept-Encoding: br" http://localhost:8000
HTTP/1.1 200 OK
content-type: text/plain;charset=UTF-8
vary: Accept-Encoding
content-encoding: br
content-length: 53
date: Sat, 19 Mar 2022 05:58:59 GMT

現在では、圧縮方式としてBrotliとgzipの2種類がサポートされています。

Deno.connect()の戻り値の変更

Deno.connect()を使うと、TCPやUnixドメインソケットによるコネクションを作成できます。

元々、このAPIはDeno.Connという型を返却していましたが、Deno v1.20で以下のような型を返すように変更されました:

これに合わせて、Deno.Connに定義されていたsetNoDelay()setKeepAlive()メソッドもDeno.TcpConnへ移動しています。

経緯などについては以下を参照ください:

Deno.listenTlscertkeyオプションがサポート

元々、Deno.listenTlsではcertFilekeyFileを使用して、証明書と秘密鍵ファイルを指定することができました。

Deno v1.20ではcertkeyオプションがサポートされ、より柔軟な指定ができるようになりました。

const cert = await Deno.readTextFile("example.crt");
const key = await Deno.readTextFile("example.key");
// certとkeyオプションで証明書と秘密鍵を文字列で渡せます
const listener = Deno.listenTls({
  hostname,
  port,
  cert,
  key,
});

これにより、例えば環境変数から証明書や秘密鍵を読み込んだりすることができるようになります。

合わせて、既存のcertFilekeyFileオプションは非推奨化されています。

パフォーマンス改善

Deno本体にdeno_opsというcrateが実装され、opの呼び出しが大幅に最適化されました。 (opについてはこちらの記事を参照)

その他には、atob/btoaの大幅なパフォーマンスが最大20倍程まで改善されています。

deno.json(c)でのimportMapオプションのサポート

deno.json(c)でImport mapsファイルを指定できるようになりました。

ここでImport mapsファイルを指定しておけば、--import-mapオプションを指定せずとも、自動で読み込まれるようになります。

{
  "importMap": "vendor/import_map.json"
}

この機能は特にdeno vendorコマンドを使うときなどに便利でしょう。

AbortSignal.timeout()がサポート

このAPIを使うと、指定された時間後に自動でキャンセルされるAbortSignalを作成することができます。

const signal = AbortSignal.timeout(3000); // 3秒後にタイムアウト
const res = await fetch(url, { signal });

fetchと組み合わせたり、テストコードなどで活用すると便利そうです。

Node.js互換モード(--compat)の改善

CJSとESM形式のモジュールの相互運用性が改善されています。

具体的には、--compat指定時にreactimportできない問題などが解消されています(内部的には、.js形式のファイルは今までESM形式で読まれていましたが、cjs形式で読まれるように修正されています)

また、CJSモジュールの動的インポートなどもサポートされています。

他にも、std/nodeへの機能追加などにより、node-mysql2パッケージがある程度動作するようになっています。

TypeScriptが4.5.2から4.6.2へアップデート

Denoに搭載されているTypeScriptのバージョンがv4.5.2からv4.6.2にアップデートされました。

TypeScript v4.6については、下記を参照ください:

その他の変更点

  • Deno.upgradeHttpという新しいAPIが追加されました。 (WebSocketなどのHTTPベースのプロトコルの実装に使用することを想定したAPIのようです)
  • TCPソケットの生成時に、非Windows環境ではデフォルトでSO_REUSEADDRが有効化されるように変更されました。
  • Deno.emit()data: URLがサポートされました。

など

参考情報