2023年のDenoの変更点やできごとのまとめ
Denoアドベントカレンダー 24日目の記事です🎅
この記事では、2023年にDenoに関して起きた出来事や大きな変更点などをまとめます。
Denoのアップデート
Node.js互換性の改善
Node.js互換性の向上のために、様々な改善や機能追加などが行われています。特に、既存のNode.jsプロジェクトをDenoで動かせるようにするための機能がいくつか導入されています。
大きな点としては、Astroが動作するようになりました。
今年、Node.js互換性に関連して実装されたもののうち、主要なものをいくつか紹介いたします。
node:
URLのサポート
node:
形式のURLを記述することで、Node.jsの組み込みパッケージをDenoから読み込めるようになりました。
import { EventEmitter } from "node:events";
const emitter = new EventEmitter();
また、node:test
のサポートも行われています。node:test
で書かれたテストコードは、通常通り、deno test
コマンドで実行できます。
import assert from "node:assert";
import { test } from "node:test";
import { sum } from "./sum.js";
test("sum", () => {
assert.strictEqual(sum(), 0);
assert.strictEqual(sum(123), 123);
assert.strictEqual(sum(3, 4, 5), 12);
});
このnode:
URLのサポートはDeno Deployでも行われており、これにより、Deno Deploy上でExpressを動かすことができるようになったようです。
package.json
のサポート
Denoがpackage.json
をサポートしました。
例えば、以下のような内容でpackage.json
が定義されていたとします。
{
"dependencies": {
"chalk": "^5.3.0"
}
}
package.json
のdependencies
に書かれたパッケージはbare specifierを指定してimport
することができます。
import chalk from "chalk";
console.info(chalk.red(chalk.bold("foobar")));
このスクリプトをdeno run
で実行しようとすると、Denoは自動でchalk
パッケージをダウンロードし、node_modules
ディレクトリを作成してくれます。
これにより、package.json
に依存したnpmパッケージなどとの互換性が向上することが期待されます。
このpackage.json
サポートが入った背景などについては、以下を参照ください。
BYONM (Bring your own node_modules)
Deno本体にBYONMという機能が実装されました。
これはnpm
やpnpm
などのパッケージマネージャーで作成されたnode_modules
ディレクトリからnpmパッケージを読み込むための機能です。
$ npm i koa
npm
でインストールされたパッケージに依存したコードを用意します。
import Koa from "koa"; // bare specifierにより読み込みができます
const app = new Koa();
app.use(async (ctx) => {
ctx.body = "Hello Deno!";
});
app.listen(3000);
BYONMは--unstable-byonm
を指定するかdeno.json
でunstable: ["byonm"]
を指定することで有効化できます。
$ deno run --unstable-byonm --allow-net --allow-read --allow-env index.js
この機能は既存のNode.jsプロジェクトをDenoで動かしやすくするために導入された機能のようです。また、npm
を利用してパッケージ管理ができるため、Denoではまだ実装されていないpostinstall
などに依存したパッケージなどが利用できるメリットもあります。
この機能を利用するには、現状、--unstable-byonm
などにより明示的に有効化する必要があります。将来的には、package.json
が検出された際はBYONMを自動で有効化することなども検討されているようです。
--unstable-bare-node-builtins
の追加
DenoからNode.jsの組み込みパッケージをnode:
なしでimport
する機能が追加されました。
この機能は--unstable-bare-node-builtins
またはdeno.json
でunstable: ["bare-node-builtins"]
を指定することで有効化できます。
import { join } from "path";
import { EventEmitter } from "events";
--unstable-sloppy-imports
)
sloppy importsのサポート (sloppy importsという機能がサポートされました。--unstable-sloppy-imports
またはdeno.json
でunstable: ["sloppy-imports"]
を指定すると有効化され、以下のような形式でのモジュールの読み込みができるようになります。
// (1) 拡張子なしでのimport
import { add } from './add';
// (2) ディレクトリを指定したimport (対象のディレクトリにindex.tsなどがあれば、それが読み込まれます)
import { foo } from './subdir';
// (3) `.ts`モジュールを`.js`拡張子でimport
import { sleep } from './subdir/sleep.js';
Object.prototype.__proto__
の有効化がサポート (--unstable-unsafe-proto
)
DenoはセキュリティのためObject.prototype.__proto__
をデフォルトで無効化しています。
新しく導入された--unstable-unsafe-proto
オプションまたはdeno.json
でunstable: ["unsafe-proto"]
を指定することで、この挙動を無効化できるようになりました。
deno.json
/deno.jsonc
deno.json
はDenoの設定ファイルです。このファイルを用意しておくことで、Denoの挙動をカスタマイズすることができます。
Import Mapsのサポート
deno.json
でImport Mapsを定義できるようになりました。
{
"imports": {
"$dax": "https://deno.land/x/dax@0.36.0/mod.ts"
}
}
deno.json
はDenoによって自動で認識され、設定内容が適用されます。このようにdeno.json
でImport Mapsを定義しておくことで、--importmap
オプションにより明示的に指定することなくImport Mapsを利用できます。
import $ from "$dax"; // => https://deno.land/x/dax@0.36.0/mod.ts
await $`echo 'foo'`;
unstable
オプションの導入
従来までは、Denoのunstable APIを利用するためには--unstable
という単一のオプションを指定する必要がありました。
--unstable
オプションの欠点として、これを指定すると、利用していないものも含め全てのunstable APIが有効化されてしまいます。
より細かく有効化したいAPIを制御できるよう、deno.json
のunstable
フィールドによって細かく制御ができるようになりました。
{
// Deno KVとDeno Cron関連のunstable APIのみを有効化します
"unstable": ["kv", "cron"]
}
この変更に合わせて、unstable APIの各カテゴリごとに--unstable-*
オプションも導入されており、CLIの実行時に有効化したいAPIを制御することも可能です。
# Deno KVとFFI関連のunstable APIのみを有効化します
$ deno run --unstable-kv --unstable-ffi main.js
compilerOptions.jsx
でprecompile
がサポート (precompiled JSX transform)
compilerOptions.jsx
でDeno独自の設定としてprecompile
がサポートされました。
これは主にSSR向けに最適化されたDeno独自のオプションで、ソースコード中のJSXテンプレートに含まれるHTMLノードをトランスパイル時にあらかじめ文字列へ変換しておくことで、オブジェクトの割り当てを減らし、パフォーマンスの向上を狙う意図があるようです。
現在では、Preactのみがこのprecompile
をサポートしていると思われますが、他のフレームワークでも実装を行うことでサポートが可能です。
Freshでもこのprecompile
の対応が進められているようで、近いうちに利用できるようになるかもしれません。
vendor
オプションの導入
deno.json
でvendor
オプションがサポートされました。
{
"vendor": true
}
このオプションにtrue
を設定すると、npm:
経由で読み込まれたパッケージはnode_modules
、それ以外の依存パッケージはvendor
ディレクトリへ自動的に保存するよう挙動が変更されます。(deno vendor
コマンドと似たような振る舞いをします)
このオプションの導入はdeno vendor
コマンドにある課題の解消が目的のようです。deno vendor
コマンドはImport Mapsの利用を前提としているため、ユーザーが用意したImport Mapsファイルとの併用が難しいことや、依存パッケージをアップデートするたびにdeno vendor
の再実行が必要になるなどの課題があり、これらの解消を目的としているようです。
globがサポート
deno.json
のexclude
やlint.exclude
などのパスを要求するフィールドにおいてglobがサポートされました。
これにより、deno fmt
やdeno lint
コマンドなどの適用対象をより柔軟に指定することができます。
{
// generated/ディレクトリまたはその子孫のディレクトリの`*.ts`ファイルを`deno fmt`や`deno lint`などの適用対象外とします
"exclude": ["generated/**/*.ts"],
}
Deno API
DenoがDeno.*
名前空間配下で提供する独自のAPIに関しても、様々な変更が行われています。
Deno KV
Deno本体とDeno DeployでDeno KVというKVSが実装されました。
Deno KVには以下のような特徴があります。
-
読み込みに関して結果整合/強整合の両方をサポート
-
DenoとDeno Deployの両方で同じAPIを使用することができます。(Deno.Kv)
-
デフォルトでは、DenoではSQLiteベースのバックエンド、Deno DeployではFoundationDBベースのマネージドサービスへデータが永続化されます。
-
上記のSQLiteやFoundationDBベースのバックエンド以外にも、KV Connect protocolを実装した独自のサーバーへ接続することも可能です。
Deno KVを利用するためには、--unstable-kv
または--unstable
の指定が必要です。
SQLiteベースのバックエンドとKV Connectサーバーの実装などはdenoland/denokvで公開されており、ユーザーがセルフホストすることも可能です。
また、Deno公式で@deno/kvというnpmパッケージも公開されています。これを利用することで、Node.jsからDeno KVへ接続することもできます。
Deno KVが発表されてから、早速、kviewやdeno-kv-insightsなどのツールも公開されています。また、awesome-deno-kvではDeno KVに関する様々な情報がまとめられています。
Deno KVの使い方や使用例などについては、23日目のWhyKさんの記事でも詳しく解説されているため、こちらも参照ください!
Deno Queues
Deno KVはDeno Queuesと呼ばれるメッセージキューとしての機能も提供しています。
Deno.Kv
オブジェクトにenqueue()
とlistenQueue()
メソッドが定義されており、これらを使うことでメッセージの配信やキューの購読などが行えます。
const kv = await Deno.openKv(":memory:");
const listenPromise = kv.listenQueue((message) => { // キューを購読します
console.info(message); // => { id: 1, payload: "foo" }
});
// メッセージを配信します。
const message = { id: 1, payload: "foo" };
const res = await kv.enqueue(message, {
keysIfUndelivered: [["dead_letter_queue", message.id]]
});
assert(res.ok);
Deno Cron
DenoとDeno DeployでDeno.cronというAPIが実装されました。
このAPIを利用することで、特定の処理を一定時間ごとに実行することができます。
Deno.cron(
"sample", // ジョブ名
{ minute: { every: 3 } }, // ハンドラーの実行間隔
// 3分ごとに以下のハンドラーが実行されます
() => doSomething(),
);
このAPIを利用するには--unstable-cron
やdeno.json
でunstable: ["cron"]
の指定などが必要です。
Deno.Command
の安定化
Denoでサブプロセスを生成するためのAPIであるDeno.Command
が安定化されました。今後は--unstable
を指定せずに利用できます。
const result = await new Deno.Command(Deno.execPath(), {
args: ["info", "--json"],
}).output();
assert(result.success);
const info = JSON.parse(new TextDecoder().decode(result.stdout));
古いAPIであるDeno.run
は非推奨化されているため、今後はDeno.Command
へ移行するとよさそうです。
Deno.serve
の安定化
Denoに組み込まれたHTTPサーバーを起動するためのAPIであるDeno.serve
が安定化されました。今後は--unstable
を指定せずに利用できます。
安定化に合わせて、Deno.serve
のシグネチャが変更されており、戻り値としてDeno.HttpServer
オブジェクトが返却されます。このオブジェクトを通してサーバーの停止(.shutdown()
)や完了の待機(.finished
)などが行えます。
const ac = new AbortController();
const server = Deno.serve(
{ signal: ac.signal },
(req) => new Response(req.body),
);
await server.finished;
Deno.Reader
/Deno.Writer
の非推奨化
Denoでファイルやソケットなどへの読み書きのために使用されていたDeno.Reader
とDeno.Writer
というAPIが非推奨化されています。
今後はReadableStream
やWritableStream
などのStream APIへの移行が推奨されます。
例えば、Deno.FsFileやDeno.ConnなどはStreams APIベースのreadable
/writable
プロパティを持っており、これらを使用して読み書きを行うこともできます。
--allow-net
/--allow-read
の指定が不要に
静的解析可能なdynamic importで今まで、dynamic importでリモートまたはローカルのモジュールを読み込むためには--allow-net
または--allow-read
の指定が必要でした。
この挙動が改善され、import()
の引数が静的に解析可能であれば、--allow-net
や--allow-read
の指定が不要になりました。
const { delay } = await import("https://deno.land/std@0.210.0/async/delay.ts");
await delay(3000);
ただし、以下のようにimport()
の引数を動的に作成しているケースでは、パーミッションを指定する必要があります。
const version = "0.210.0";
const { delay } = await import(`https://deno.land/std@${version}/async/delay.ts`);
await delay(3000);
--deny-*
オプションの導入
Denoに特定の操作を明示的に拒否させるために、--deny-*
オプションが実装されました。
このオプションは--allow-*
オプションと併用することが可能で、その場合、--deny-*
で指定された内容が優先されます。
# ファイルへの書き込み以外の操作を許可します。
$ deno run --allow-all --deny-write main.js
# README.mdを除く任意のファイルの読み込みを許可します。
$ deno run --allow-read --deny-read=README.md main.js
deno test
コマンドの新機能
JUnit/dot/TAPレポーターがサポート
deno test
コマンドで--reporter
オプションと--junit-path
がサポートされました。
これらのオプションを活用することで、Denoがテスト結果を表示する際のフォーマットをカスタマイズできます。
TAPレポーターの使用例:
$ deno test --allow-read --reporter=tap | deno run --allow-read npm:tap-nyan@1.1.0
10 -_-_-_-_-__,------,
0 -_-_-_-_-__| /\_/\
0 -_-_-_-_-_~|_( ^ .^)
-_-_-_-_-_ "" ""
Pass!
dot/JUnitレポーターの使用例:
# JUnitレポーターを有効化します
$ deno test --allow-read --allow-env --reporter=junit
# dotレポーターで標準出力に結果を表示しつつ、report.xmlにJUnit形式のレポートを出力します
$ deno test --allow-read --allow-env --reporter=dot --junit-path=report.xml
deno doc
コマンドの新機能
deno doc --html
deno doc
コマンドの--html
オプションにより、APIドキュメントを静的なHTMLファイルとして出力できるようになりました。
--output
オプションによりHTMLの出力先を変更できます。(デフォルトはdocs/
ディレクトリ)
$ deno doc --html --output=api-docs --name=fresh-testing-library mod.ts
Written 261 files to "./api-docs/"
生成されたHTMLはブラウザで直接閲覧できます。
$ xdg-open ./api-docs/index.html
deno doc --lint
deno doc
コマンドに--lint
オプションが導入されています。これを指定することで、指定されたファイルに含まれるJSDocコメントの検証が行えます。
export
されているAPIにJSDocコメントが記載されていない場合や、戻り値の型定義が省略されている箇所などに対して警告を表示してくれます。
$ deno doc --lint server.ts
Missing JSDoc comment.
at file:///path/to/server.ts:36:3
...
Missing explicit type.
at file:///path/to/server.ts:321:14
error: Found 24 documentation lint errors.
$ echo $?
1
パッケージマネージャーの実験的な実装
Deno本体に実験的にパッケージマネージャーの実装が進められています。
これは、以下のようにjsr:
形式のURLを記述しておくとで、Denoが独自のパッケージレジストリと連携してsemverの解決などを行いつつ、パッケージのインストールなどを自動で行ってくれる機能のようです。
import { foo } from "jsr:@foo/some_pkg@1/mod.ts";
この機能はおそらくdeno:
URLとして実装が検討されていた機能に相当するものだと思われます。
最近公開された以下の動画では、このjsr:
URLやDeno v2に関する話について少し触れられています。
また、この機能に関連してワークスペースのサポートやパッケージの公開機能なども開発が進めらているようです。
deno jupyter
コマンドの導入
Deno本体にJupyterカーネルが実装されました。deno jupyter
を利用することで、Jupyterカーネルのインストールなどが可能です。
$ deno jupyter --unstable --install
[InstallKernelSpec] Installed kernelspec deno in /path/to/jupyter/kernels/deno
✅ Deno kernelspec installed successfully.
インストールが完了すると、Deno Kernelが利用できます。
$ jupyter console --kernel deno
この対応に合わせて、deno fmt
コマンドでも.ipynb
のフォーマットがサポートされています。
deno compile
の改善
deno compile
コマンドは、指定されたエントリポイントを元にスタンドアロンの実行可能ファイルを生成してくれるコマンドです。
このdeno compile
でdynamic importなどのサポートの強化が行われています。import()
の引数を動的に作成しているようなケースでもdeno compile
が認識してくれます。
また、先述のBYONMがdeno compile
でもサポートされています。BYONMとdeno compile
を併用することで、例えば、Node.js製のCLIアプリからスタンドアロンの実行可能ファイルを生成することなども実現できそうです。
deno bundle
コマンドの非推奨化
Denoに組み込まれたバンドラであるdeno bundle
コマンドが非推奨化されました。
今後はdenoland/deno_emitやesbuild, Rollupなどへの移行が推奨されます。
--unstable-hmr
)
HMRの実験的なサポート (Denoに--unstable-hmr
というオプションが追加されました。
基本的には--watch
オプションと同様の振る舞いをしますが、ファイルが変更された際に、可能な際はプロセス全体を再起動せずに、変更されたファイルのみに対してその場でパッチを当ててくれます。
$ deno run --unstable-hmr=data.json --no-clear-screen mod.ts
ファイルの変更が検出された場合は"hmr"
イベントを発火してくれるため、フレームワークなどで変更を検出することもできます。
addEventListener("hmr", (e) => {
onHMR(e.detail.path);
});
.env
のサポート (--env
オプション)
Deno本体で.env
の読み込みがサポートされています。--allow-read
を指定せずに.env
の読み込みが可能です。
$ deno run --env main.js
--env
オプションにパスを指定することで、読み込むファイルをカスタマイズできます。
$ deno run --env=.env.development main.js
deno_std
にもdotenv
モジュールがありますが、--env
にはない便利な機能などを提供していることもあり、今のところ削除されずに残されています。
deno coverage
の機能追加
HTMLレポーターの実装
HTMLレポーターを利用すると、カバレッジレポートを静的なHTML形式で出力してくれます。
$ deno coverage --html ./coverage
HTML coverage report has been generated at file:///path/to/project/coverage/html/index.html
デフォルトの出力形式の変更
deno coverage
でのデフォルトのレポートの出力フォーマットが以下のように変更されています。
$ deno coverage ./coverage
-----------------------------------------------------------
File | Branch % | Line % |
-----------------------------------------------------------
demo/components/Button.tsx | 100.0 | 0.0 |
demo/fresh.gen.ts | 100.0 | 100.0 |
... 省略 ...
internal/test_utils/mod.ts | 100.0 | 100.0 |
server.ts | 83.3 | 90.3 |
-----------------------------------------------------------
All files | 82.2 | 72.3 |
以前までのフォーマットでレポートを出力したい場合は、--detailed
オプションを指定する必要があります。
deno test
の--coverage
オプションにデフォルト値が設定
以前まではdeno test --coverage
でカバレッジ情報を出力するためには、明示的に出力先のディレクトリを指定する必要がありました。
$ deno test --coverage=cov/ -A ./server.test.ts
この挙動が変更されて、--coverage
へのディレクトリの指定が任意に変更されました。省略した場合は、デフォルトで./coverage/
にカバレッジ情報が出力されます。
deno_std
deno_std
はDeno公式の標準ライブラリです。HTTP関連のユーティリティやCSV/TOMLパーサー、ユニットテスト向けのユーティリティなど、様々な機能を提供してくれます。
v1のリリースについて
deno_std
のv1のリリースに向けて、モジュール構造の整理や命名などの規約の統一、需要が高くないモジュールの非推奨化や削除などの対応が進められています。
以下は進められている対応の例です。
モジュール階層の見直し
既存のいくつかのモジュールや機能の配置場所が見直されています。
-
std/collections
で提供されていたRedBlackTree
などの各種データ構造がstd/data_structures
へ移動されています。 -
std/encoding
配下のcsv
/front_matter
/json(c)
/toml
/yaml
モジュールがトップレベルに移動されました。(例:std/encoding/csv
がstd/csv
、std/encoding/json
がstd/json
に移動しています。) -
std/testing/asserts.ts
がstd/assert
へ移動しています。 -
std/flags
がstd/cli
へ移動しています。(CLI関連の機能は、今後はこのstd/cli
へ追加されるようです。)
非推奨モジュールの削除
主要なものとして、例えば、以下のモジュールが削除されています。
それ以外にもstd/io/types.d.tsやstd/permissionsなども非推奨化されており、今後、削除される予定のようです。
各public APIごとのファイルの分割
今まで、各モジュールの機能は基本的にmod.ts
という単一のファイルでまとめて提供されていました。必要な機能のみを選択的にimport
できるようにするため、いくつかのモジュールが各public APIごとにファイルが分割されています。
例えば、std/path
のextname()
がmod.ts
だけでなくextname.ts
からもimport
できるようになります。(mod.ts
自体は残っているため、引き続き利用することも可能です)
std/node
の削除
Deno本体でのnode:
URLのサポートに伴い、deno_std
で提供されていたNode.js互換レイヤー(std/node
)は削除されました。
Node.jsの組み込みパッケージの実装は現在はDeno本体に組み込まれています。これにより、Node.js互換機能を利用する際にstd/node
のダウンロードが不要になるため、起動の高速化が期待されます。また、Deno本体に実装が組み込まれることにより、例えばnode:vm
やnode:child_process
などといったパッケージの開発がより行いやすくなることが推測されます。
新規モジュール
deno_std
にいくつかの新規モジュールが追加されています。
モジュール | 概要 |
---|---|
std/expect | Jestライクなexpect() APIを提供します |
std/ulid | ULIDの実装が提供されます。 |
std/url |
URL に関するユーティリティが提供されます |
std/cli | 元々、std/flags で提供されていた機能に加え、スピナーなどのCLI関連の機能を提供します。 |
std/data_structures | 元々、std/collections に配置されていた各種データ構造が提供されます。 |
std/msgpack | MessagePackのエンコーディング/デコーディングをサポートします。 |
std/ini | INIファイルの読み書きをサポートします |
std/webgpu | WebGPU APIに関するユーティリティを提供します。 |
std/net | ネットワーク関連のユーティリティを提供します(現在は、システムの空きポートを返却するgetAvailablePort のみが提供されています) |
Deno Deploy
Deno DeployはDeno公式のサーバーレスTypeScript/JavaScriptホスティングサービスです。
Deno Subhostingが公開
Netlify Edge Functionsなどの基盤として利用されているDeno Subhostingというサービスが公開されました。
Deno Subhostingを利用することで、プラットフォーム提供者はDeno Deployの基盤を活用して、ユーザーから提供されたTypeScript/JavaScriptコードを安全に実行するための仕組みを構築することができるようです。
npmパッケージがサポート
Deno Deployでもnpm:
URLがサポートされ、npmパッケージを利用したコードが実行できるようになりました。以下の公式ブログではExpressやFastifyなどを使用したWebアプリケーションを動作させる例が公開されています。
deno.land/xなどで公開されているものに加えてnpmパッケージも利用できるようになるため、選択肢が大きく広がりそうです。
Changelogページが公開
Deno DeployのChangelogページが公開されています。RSSフィードも提供されているため、もしDeno Deployの情報などを知りたい場合はこちらを購読しておくとよさそうです。
その他
Deno Fest
今年は国内でDeno Festという大きなイベントが開催されました。以下のページでレポートが公開されています。
Deno社の開発メンバーの方も参加をされており、講演内容がDenoのYoutubeチャンネルで公開されています。
- Ryan Dahl @ DenoFest Tokyo, "Deno ❤️ npm"
- Luca Casonato @DenoFest Tokyo: "The state of Fresh"
- Kevin Whinnery @ DenoFest Tokyo, "The state of web frameworks in Deno"
- Divy Srivastava @ DenoFest Tokyo, "Blazing fast FFI in Deno"
おわりに
Denoに関しては、今年はNode.js互換性の改善とDeno KVなどのDeno Deployのユースケースを広げるための機能の開発などが中心に行われている印象でした。
Deno QueuesやDeno Cronなどにより、Deno Deploy上でバックグラウンドジョブシステムの仕組みなどを構築するための仕組みが少しずつ整いつつあるようです。
また、国内でのDeno Festというイベントの開催や22日目の@lef237さんの記事でのDenoやFreshなどを活用したプロダクト開発の話など、国内でも少しずつDenoの認知や使用などが広がりつつある印象を感じました。
明日はkt3kさんの「Deno Standard Library」です!
Discussion