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

この記事では主な変更点などについて解説します。

deno doc

deno doc --lint

deno docコマンドで--lintオプションがサポートされました。

$ deno doc --lint mod.ts
Missing JS documentation comment.
    at file:///home/uki00a/ghq/github.com/uki00a/some-project/mod.ts:12:3

このオプションを指定すると、DenoはAPIドキュメントを出力する前に、各APIを検査します。そして以下のようなAPIが検出された場合、APIドキュメントを出力せずにエラーを表示します。

  • exportされているAPIにJSDocコメントが書かれていない場合 (Missing JS documentation comment.)
  • exportされているAPIの戻り値の型定義が省略されている場合 (Missing return type.)
  • exportされている型のプロパティがexportされていない型を参照している場合 (Type 'foo' references type 'Bar' which is not exported from a root module.)
    • このエラーが発生した場合、そのexportされていない型に@internalを指定することでエラーを抑止できます

deno doc --html

deno docでAPIドキュメントのHTML形式での出力がサポートされました。 --htmlを指定すると、docsディレクトリにHTMLが出力されます。

$ deno doc --html --name=fresh-testing-library mod.ts
Written 261 files to "./docs/"

docs/index.htmlを開くことで作成されたAPIドキュメントを閲覧できます。

# ファイルサーバーを起動します
$ deno run --allow-read --allow-net https://deno.land/std@0.205.0/http/file_server.ts ./docs

# ブラウザを開きます
$ xdg-open http://localhost:4507

APIドキュメントの保存先ディレクトリを変更したい場合は、--outputオプションを指定します。

$ deno doc --html --output=api-docs --name=fresh-testing-library mod.ts

公開APIから参照されている非公開APIの出力がサポート

例えば、以下のようなファイルがあったとします。

interface BaseOptions {
  port: number;
}

export interface ServerOptions extends BaseOptions {
  signal: AbortSignal;
}

非公開APIであるBaseOptionsは公開APIであるServerOptionsから参照されています。この場合、非公開APIであるBaseOptionsの情報も出力されます。(v1.37までは出力されませんでした)

$ deno doc mod.ts
Defined in file:///home/uki00a/ghq/github.com/uki00a/some-project/mod.ts:1:1

private interface BaseOptions

****  port: number

Defined in file:///home/uki00a/ghq/github.com/uki00a/some-project/mod.ts:5:1

interface ServerOptions extends BaseOptions

  signal: AbortSignal

複数エントリポイントの指定がサポート

例えば、v1.37まではdeno docコマンドに複数のエントリポイントを指定すると、以下のようなエラーが発生します。

$ deno doc server.ts expect.ts
error: Node expect.ts was not found!

v1.38ではこの挙動が改善され、複数のエントリポイントを引数として指定できるようになりました。例えば、以下のケースだと、server.tsexpect.tsそれぞれのAPIドキュメントが出力されます。

$ deno doc server.ts expect.ts

この変更による影響として、フィルタリングを行いたい際は、今後は明示的に--filterオプションを指定する必要があります。

$ deno doc --filter=createHandlerContext server.ts expect.ts

HMRがサポート (--unstable-hmr)

Denoに--unstable-hmrというオプションが追加されました。

基本的には--watchオプションと同様の振る舞いをしますが、ファイルが変更された際に、可能な際はプロセス全体を再起動せずに、変更されたファイルのみに対してその場でパッチを当ててくれます。

$ deno run --unstable-hmr=config.json --no-clear-screen mod.ts

ただし、--unstable-hmrに指定されたファイルが変更された場合など、状況によっては--watchと同様にプロセス全体が再起動されることもあるようです。

また、ファイルの変更が発生した際にDenoはhmrイベント(CustomEvent)を発火するため、状態の更新など必要に応じて独自の処理を実装することも可能です。

function onHMR(path: string) {
  // ...
}

addEventListener("hmr", (e) => {
  onHMR(e.detail.path);
});

unstable APIの詳細な制御がサポート (--unstable-*オプション)

Denoにはいくつかのunstable APIが存在します。(例: Deno.openKv, Deno.dlopenなど)

こういったAPIを利用するためには、--unstableオプションを指定する必要がありました。しかし、--unstableオプションはユーザーが直接利用しないすべてのunstable APIを有効化します。

より細かく有効化したいAPIを制御するために、--unstable-ffi/--unstable-kv/--unstable-cronなど、各カテゴリごとにunstable APIを有効化するためのオプションが実装されました。

例えば、--unstable-kvを指定した際は、unstable APIのうちDeno KV関連のAPIのみが有効化されます。

$ deno run --unstable-kv main.js

現時点では以下のオプションが提供されています。

  • --unstable-broadcast-channe
  • --unstable-ffi
  • --unstable-fs
  • --unstable-http
  • --unstable-kv
  • --unstable-net
  • --unstable-worker-options
  • --unstable-cron (後述)
  • --unstable-bare-node-builtins (後述)
  • --unstable-byonm (後述)

deno.json

unstableフィールドが追加

--unstable-*オプションの実装に関連して、deno.jsonでも有効化したいunstable APIを細かく管理できるように改善されました。

{
  "unstable": ["kv", "cron"]
}

compilerOptions.jsxprecompileがサポート

compilerOptions.jsxでDeno独自の設定としてprecompileがサポートされました。

precompileの導入の目的として、ソースコード中のJSXテンプレートに含まれるHTMLノードをトランスパイル時にあらかじめ文字列へ変換しておくことで、オブジェクトの割り当てを減らし、SSR時のパフォーマンスの向上を狙う意図があるようです。

基本的にはPreact/Freshでの使用を想定したオプションであると思われますが、サポートを入れることでそれ以外のフレームワークでも利用は可能なようです。

Preactでは現在、このprecompile向けのサポートが進んでいるようで、近いうちに利用できるようになるかもしれません。

また、以下のリポジトリではPreact+precompileオプションの使用例が公開されています。


.envのサポート (--envオプション)

deno rundeno testなどのコマンドで--envオプションが実装されました。このオプションを指定すると、コードを実行する前にDenoが.envから環境変数を読み込んでくれます。

$ deno run --env main.js

--allow-readを指定せずに.envの読み込みが行えることが利点のようです。

もし.env以外から環境変数を読みたい場合は、--envオプションにパスを指定する必要があります。

$ deno run --env=.env.development main.js

deno replでJSXがサポート

deno replでJSXがサポートされました。

$ deno repl
> /** @jsxImportSource https://esm.sh/preact@10.18.1 */
undefined

> const vnode = <div>foobar</div>;
┌ ⚠️  Deno requests net access to "esm.sh".
✅ Granted net access to "esm.sh".
undefined

Deno API

Deno.cron (unstable)

新しいAPIとしてDeno.cronが実装されています。

このAPIを利用する際はdeno.jsonunstableオプションや--unstable-cronオプションなどで有効化できます。

{
  "unstable": ["cron"]
}

このAPIを利用することで、Cron式に指定された内容に基づいてハンドラーを定期的に実行することができます。

Deno.cron(
  "sample", // ジョブ名
  "*/1 * * * *", // cron式
  async () => { // ハンドラー
    await doSomething();
  },
);

Deno CLIの実装においては、Cronに関する状態はメモリ上にのみ保持されディスクには永続化されないようなので、少し注意が必要かもしれません。

リトライについて

もしハンドラーの実行に失敗した場合、最大で5回まで自動的にリトライされます。リトライ間隔はデフォルトで100ms → 1s → 5s → 30s → 1mのようです。(ext/cron/local.rs#L32)

この挙動はbackoffScheduleオプションによりカスタマイズが可能で、例えば、以下のケースだと1s → 2s → 4sの間隔で最大3回までリトライが行われます。

Deno.cron(
  "sample", // ジョブ名
  "*/1 * * * *", // cron式
  async () => { // ハンドラー
    await doSomething();
  },
  {
    backoffSchedule: [1000, 2000, 4000],
  },
);

ジョブの中断

Deno.cronにはsignalオプションを指定することができます。指定されたAbortSignalが中断状態に変わると、Denoは対象のジョブの登録を解除します。

const ac = new AbortController();
// ...
await Deno.cron(
  "sample",
  "*/1 * * * *",
  async () => {
    await doSomething();
  },
  {
    signal: ac.signal,
  },
);

また、Deno.cronは戻り値としてPromiseを返却しますが、このPromiseAbortSignalが中断されるとresolveされます。


Deno.serve

意図を明確にするために、Deno.ServerDeno.HttpServerへリネームされました。Deno.Serverは非推奨化されており、今後、削除される予定です。

usingのサポート

以下の各オブジェクトでSymbol.dispose/Symbol.asyncDisposeが実装されています。

  • Symbol.dispose
    • Deno.FsFile
    • Deno.FsWatcher
    • Deno.ChildProcess
    • Deno.Kv
    • Deno.Listener
    • Deno.Conn
  • Symbol.asyncDispose
    • Deno.HttpServer

使用例:

{
  using kv = await Deno.openKv(":memory:");
  console.info(await kv.get(["users", 1]));
}

{
  using f = await Deno.open("deno.json");
  console.info(await f.stat());
}

Deno KV

リモートバックエンドでもexpireInオプションがサポートされました。

Node.js互換性

npmなどで作成されたnode_modulesのサポート (BYONM)

npmなどで作成されたnode_modulesディレクトリからのnpmパッケージの読み込み(BYONM)がサポートされました。

例えば、以下のようにnpmkoaパッケージがインストールされていたとします。

$ npm i koa

npmによってインストールされたkoaパッケージをnpm:なしで読み込むJavaScriptファイルを用意します。

// index.js
import Koa from "koa";

const app = new Koa();

app.use(async (ctx) => {
  ctx.body = "Hello Deno!";
});

app.listen(3000);

この状態で--unstable-byonmオプションを指定するか または deno.jsonunstable: ["byonm"]を設定すると、npmによって作成されたnode_modulesディレクトリからkoaパッケージが読みこまれます。

$ deno run --unstable-byonm --allow-net --allow-read --allow-env index.js

将来的には、package.jsonが存在する際は--unstable-byonmの挙動をデフォルト化することも検討されているようです。

node:なしでのNode.js組み込みパッケージの読み込み (--unstable-bare-node-builtins)

Node.jsの組み込みパッケージのnode:なしでの読み込みがサポートされました。(基本的には、node:を付けて読み込むことが推奨されます)

import { EventEmitter } from "events";

const emitter = new EventEmitter();
emitter.once("foo", console.info);
emitter.emit("foo", 123);

この機能は以下のいずれかの方法で有効化できます。

  • BYONMを有効化する
  • --unstable-bare-node-builtinsを指定する
  • deno.json"unstable": ["bare-node-builtins"]を設定する

その他の改善

  • EventSourceが実装されました
  • WebSocket over HTTP/2が実装されました
  • Deno本体のV8がv12へアップデートされました (Array.fromAsync/Promise.withResolversが利用できるように)
  • Object.groupByMap.groupByの型定義が追加されました
  • deno test: --junit-pathオプションで指定されたディレクトリが存在しない場合でも動作するように改善されました。
  • deno lsp: deno.jsonがない場合は、フォーマット時にtypescript.preferences.quoteStyleが参照されるように改善されました。

参考