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";
sloppy importsのサポート (--unstable-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プロパティを持っており、これらを使用して読み書きを行うこともできます。
静的解析可能なdynamic importで--allow-net/--allow-readの指定が不要に
今まで、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などへの移行が推奨されます。
HMRの実験的なサポート (--unstable-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