2022年のDenoの変更点やできごとのまとめ
Denoアドベントカレンダー 25日目の記事です🎅
この記事では、2022年にDenoに関して起きた出来事や大きな変更点などをまとめます。
Denoの今後の方針について
今年の8月にDeno公式から今後の開発方針などが発表されました。
具体的には、以下の点などに注力していくことが発表されています。
- Node.jsとの互換性の向上
- パフォーマンスの向上
- 開発体験の向上
それぞれの詳細については後ほど紹介いたしますが、これらの方針を実現するために、Deno本体へのnpmパッケージサポートや新しい高速なHTTPサーバの導入などが実施されました。
Deno本体のアップデート
npmパッケージがサポート
Denoでnpmパッケージが利用できるようになりました。
以下のように、npm:<パッケージ>@<バージョン>
のような形式でimport
文を記述することでnpmパッケージを利用できます。
// @deno-types="npm:@types/koa@2.13.5"
import Koa from "npm:koa@2.13.4";
const app = new Koa();
app.use(async (ctx) => {
ctx.body = "Hello Deno!";
});
app.listen(3000);
このコードは通常通りdeno run
コマンドで実行することができます。
また、ネットワークアクセスなどを許可するために、パーミッションフラグの指定も必要です。
$ deno run --allow-net --allow-read --allow-env main.js
import
されたnpmパッケージはdeno.land/xで公開されているDenoモジュールと同様に、初回実行時のみダウンロードされ、グローバルキャッシュ(DENO_DIR
)に保存されます。
このようにDeno本体にnpmパッケージサポートが入ったことで、Node.jsで開発されたライブラリやツールなどの資産を活用できる余地が生まれました。
実際にこのnpmパッケージサポートを利用して、Denoでtextlint
を動かす例がkn1chtさんの18日目の記事で紹介されています。
型チェックの挙動に関する変更
今まで、Denoではdeno run
やdeno cache
などのコマンドを実行する際に、自動でTypeScriptによる型チェックを行っていました。
しかし、型チェックは時間のかかる処理であり、場合によっては、deno run
などによるスクリプトの実行時間に影響を与えてしまいます。
また、開発時にLSPを利用している場合、コードの編集時に事前に型エラーに気づくことも少なくないはずです。
そういった背景もあり、Deno v1.23以降のバージョンからdeno run
やdeno cache
などのコマンドの実行時に、DenoはデフォルトでTypeScriptの型チェックを実施しなくなりました。
もし明示的に型チェックを実施したい場合は、新しく導入されたdeno check
コマンドが利用できます。
CIなどで型チェックを実施したい場合は、このコマンドを利用するとよいと思います。
$ deno check mod.ts
deno task
コマンド)が実装
タスクランナー(Deno本体にタスクランナーが実装されました。
この機能を利用するには、deno.json
のtasks
にタスクを定義する必要があります。
{
"tasks": {
"test": "RUST_BACKTRACE=1 deno test --allow-read=tests/testdata tests",
"check": "deno check mod.ts && deno lint && deno fmt --check"
}
}
deno task
というコマンドを使うことで、定義したタスクを実行できます。
$ deno task test
deno task
コマンドの大きな特徴としてクロスプラットフォーム対応が意識されており、Node.jsにおけるcross-env相当の機能やcp
やmv
などのコマンドがあらかじめ組み込まれています。
--prompt
の挙動がデフォルト化
Denoにはパーミッションシステムが組み込まれています。
これは明示的に権限を与えない限り、ネットワークやファイルシステム、環境変数などへのアクセスを禁止するための仕組みです。
例えば、以下のようにファイルを読み込みたい場合は、Denoを実行する際に--allow-read
オプションを明示する必要がありました。
const content = await Deno.readTextFile("README.md");
セキュリティなどの観点からすると、この仕組みは有用ではあるものの、ちょっとしたスクリプトを記述するときなどはどうしてもオプションの指定が面倒になりがちです。
この課題への解決策の一つとして、Denoは--prompt
オプションを提供していました。
このオプションを有効化することで、許可されていない処理を実行する際に、以下のようにプロンプトが表示されます。
$ deno run --prompt main.js
⚠️ ┌ Deno requests read access to "main.js".
├ Requested by `Deno.readTextFile()` API
├ Run again with --allow-read to bypass this prompt.
└ Allow? [y/n] (y = yes, allow; n = no, deny) >
ここでy
を入力すると、main.js
への--allow-read
権限が許可されます。
そして、今年リリースされたDeno v1.19において、この --prompt
を指定した際の挙動がデフォルト化されました。
そのため、--prompt
を指定されていなくても、許可されていない処理を実行する際は上記のようなプロンプトが表示されます。
もし以前までの挙動に戻したい場合は--no-prompt
オプションを指定するか、もしくはDENO_NO_PROMPT
環境変数に1
を設定する必要があります。
設定ファイルの自動読み込み
Denoには設定ファイルによって挙動をカスタマイズする仕組みがあります。
この仕組みを利用することで、TypeScriptのcompilerOptions
のカスタマイズやdeno test
によるテスト対象ファイルの設定などを行うことができます。
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"importMap": "./import_map.json",
"test": {
"files": {
"include": ["./tests/"]
}
}
}
以前までは、Denoに設定ファイルを読み込ませるためには、--config
オプションを指定する必要がありました。
$ deno run --config deno.json main.ts
今年リリースされたDeno v1.18では、この設定ファイルの自動読み込みがサポートされました。
もしdeno.json
またはdeno.jsonc
というファイルが存在する場合、--config
オプションが指定されていなくても、Denoは自動でそれを読み込みます。
もしこの挙動を無効化したい場合は、--no-config
オプションを指定する必要があります。
ロックファイルの自動適用
(補足) ロックファイルとは
Denoにはアプリケーションが依存するサードパーティモジュールの内容を元にハッシュ値を計算し、ロックファイルと呼ばれるファイルに保存する機能があります。
アプリケーションを実行する際にサードパーティモジュールから再度ハッシュ値を計算し、ロックファイルに書き込まれたハッシュ値と比較することで、サードパーティモジュールの内容が改ざんされていないことを保証することができます。(インテグリティチェック)
今年リリースされたDeno v1.28において、ロックファイルの自動適用がサポートされました。
deno.json
またはdeno.jsonc
が存在する場合、ロックファイルの生成とインテグリティチェックの適用が自動化されます。
Denoによってdeno.lock
というファイルが自動で生成されるため、必要に応じてバージョン管理に含めることを検討するとよさそうです。
もしこの挙動を無効化したい場合は、--no-lock
オプションを指定するか、deno.json
でlock: false
を指定する必要があります。
{
"lock": false
}
新しいHTTPサーバ (Flash)
元々、DenoにはHTTPサーバが組み込まれていました。(Deno.serveHttp
)
現在、Denoではパフォーマンスの向上が重要視されており、このHTTPサーバについてもさらなる高速化が目指されていました。
そこで、Flashと呼ばれる新しいHTTPサーバが実装されました。
下記のようにDeno.serve
というAPIを利用することで、Flashを起動することができます。
const ac = new AbortController();
Deno.serve((req) => new Response("Hello, Flash"), {
port: 4500,
onListen: ({ port, hostname }) => {
console.log(`Started at http://${hostname}:${port}`);
},
onError: (error) => {
return new Response("Internal Server Error", { status: 500 });
},
signal: ac.signal,
});
Deno.serve
によって起動されたHTTPサーバ(Flash)は、既存のDeno.serveHttp
と比較して、3倍近くの高速化が実現されているようです。
Deno.serve
はまだ不安定機能という位置づけであり、今後もさらなる開発が予定されているようです。
Deno.Command
- 新しいサブプロセスAPI
DenoにはDeno.run
というサブプロセスを起動するためのAPIがあります。
このAPIを利用することで大抵のユースケースはカバーできるものの、このDeno.run
では目的を満たすことが難しいケースなどが存在することが明らかになってきました。
例)
- Deno本体でのNode.js互換性の向上のために、コマンドを同期的に実行できる機能が必要
- 単純に特定のコマンドの実行結果にだけ興味があり、細かい制御が不要なケースではやや冗長になってしまうことがある
こういった課題の解消などを目的として、より柔軟にサブプロセスを操作するために、Deno.Command
という新しいAPIが導入されました。
例えば、コマンドの実行結果を同期的に取得したい場合は、Deno.Command#outputSync
が利用できます。
const command = new Deno.Command("deno", {
args: ["info", "--json"],
});
const status = command.outputSync();
if (status.success) {
console.info(new TextDecoder().decode(status.stdout));
}
また、より細かな制御が必要なケースに備えて、Deno.Command#spawn
が提供されています。
const command = new Deno.Command("deno", {
args: ["fmt", "--ext=json", "-"],
stdin: "piped",
stdout: "piped"
});
const child = command.spawn();
const file = await Deno.open("deno.json");
file.readable.pipeTo(child.stdin);
const status = await child.status;
if (status.success) {
const output = [];
for await (const x of child.stdout.pipeThrough(new TextDecoderStream())) {
output.push(x);
}
console.info(output.join(''));
}
このAPIの導入に関するより詳しい背景などについては、kt3kさんの11日目の記事なども参照ください。
Deno.test
のサブステップAPIが安定化
Deno.test
では、テストケースのグループ化などを目的としてサブステップAPIというものを提供しています。
Deno.test("nested test case", async (t) => {
const success = await t.step("step 1", async (t) => {
const success = await t.step("step 1-1", () => {
throw new Error("Failed!");
});
if (!success) throw new Error("Failed!");
await t.step("step 1-2", () => {});
});
if (success) throw new Error("Failed!");
});
Deno v1.18でこのサブステップAPIが安定化されました。
今後は--unstable
を指定しなくてもこのAPIを利用できます。
deno bench
コマンド
Deno v1.20でDeno本体にベンチマークの仕組みが導入されました。
function sum(...numbers: Array<number>): number {
return numbers.reduce((a, b) => a + b, 0);
}
Deno.bench("sum", () => {
sum(1, 2, 3, 4, 5);
});
deno bench
コマンドを実行することで、Deno.bench
によって定義されたベンチマークを実行することができます。
$ deno bench
file:///home/uki00a/ghq/github.com/uki00a/sandbox/sum_bench.ts
benchmark time (avg) (min … max) p75 p99 p995
------------------------------------------------- -----------------------------
sum 7.04 ns/iter (5.41 ns … 102.02 ns) 7 ns 17.91 ns 21.43 ns
ベンチマークを記述する際は、ファイル名は以下のように命名する必要があります。(拡張子は.js
や.tsx
などでも有効です)
bench.ts
foo.bench.ts
foo_bench.ts
deno vendor
コマンド
サードパーティモジュールをバージョン管理に含めるのを容易にすることなどを目的に、deno vencor
という新しいコマンドが実装されました。
詳細については、以下の記事などを参照いただければと思います。
deno init
コマンド
プロジェクトの初期生成を容易にすることを目的に、deno init
という新しいコマンドが実装されています。
このコマンドを実行すると、Denoの設定ファイルやエントリポイントなどが自動で生成されます。
$ deno init my-deno-project
✅ Project initialized
Run these commands to get started
cd my-deno-project
// Run the program
deno run main.ts
// Run the program and watch for file changes
deno task dev
// Run the tests
deno test
// Run the benchmarks
deno bench
今後、Denoで新しくプロジェクトを作成される際は使用してみるとよいかもしれません。
deno_stdのアップデート
Deno本体以外にも、公式の標準ライブラリであるdeno_stdでも様々な変更が行われています。
Web Streams APIへの移行
元々、deno_stdで提供される多くのモジュールはDeno.Reader
やDeno.Writer
などのGoに影響を受けたAPIをベースに実装されていました。
しかし、Deno本体でWeb Streams APIの実装などが進んだこともあり、deno_stdの多くのモジュールがWeb Streams APIへ移行されつつあります。
それに関連し、std/encoding/json/stream.tsやstd/streams/delimiter.ts、std/encoding/csv/stream.tsなどの新しいモジュールが追加されたり、std/textprotoの削除など様々な変更が行われました。
例えば、以下はstd/encoding/json/stream.ts
の使用例です。
import { JsonParseStream } from "https://deno.land/std@0.170.0/encoding/json/stream.ts";
import { readableStreamFromIterable } from "https://deno.land/std@0.170.0/streams/conversion.ts";
const readable = readableStreamFromIterable([
`{"name": "foo"}`,
`{"a": 1, "b": true}`,
]).pipeThrough(new JsonParseStream());
for await (const json of readable) {
console.log(json);
// Output:
// { name: "foo" }
// { a: 1, b: true }
}
deno_stdに限らず、Deno本体においてもDeno.Reader
などのAPIの削除が検討されているようで、将来的にはこのWeb Streams APIベースのAPIがDenoにおける主流となる可能性もありそうです。
std/dotenv
新しいモジュールとして、std/dotenv
が追加されています。
このモジュールは.env
ファイルの解析や読み込みなどの機能を提供します。
import { load } from "https://deno.land/std@0.170.0/dotenv/mod.ts";
const config = await load({
restrictEnvAccessTo: ["MONGO_URL", "REDIS_URL"],
});
std/testing
の拡充
ユニットテストなどに関わる機能を提供するstd/testing
モジュールで、BDDスタイルでテストを記述するための機能が実装されました。
import { assertEquals } from "https://deno.land/std@0.170.0/testing/asserts.ts";
import { describe, it } from "https://deno.land/std@0.170.0/testing/bdd.ts";
import { sum } from "./sum.ts";
describe("sum", () => {
it("should return sum of numbers", () => {
assertEquals(sum(1, 2, 5), 8)
});
it("should return 0 when no arguments are given", () => {
assertEquals(sum(), 0);
});
});
これ以外にも、std/testing
には様々な機能が実装されました。
- std/testing/mock - テストスパイやスタブなどの機能を提供します。
- std/testing/snapshot - スナップショットテストの仕組みを提供します。
- std/testing/types - TypeScriptの型定義を検証する仕組みを提供します。
Deno Deployのアップデート
Deno DeployはDeno公式のJavaScript/TypeScriptコードをCDNエッジ環境で実行するためのホスティングサービスです。
Deno DeployのBeta 4がリリース
Deno DeployでBeta 4がリリースされました。
このリリースでは、有料プランであるProプランが公開されています。
各プランについては、以下の記事でまとめられているため、詳しくはそちらを参照いただければと思います。
また、Deno Deployの管理画面もリニューアルされており、メトリクスの可視化やログの改善など使い勝手が向上されています。
deployctl v1
Deno Deploy公式のCLIツールであるdeployctlのv1がリリースされました。
deployctl deploy
コマンドやGitHub Actionによる自動デプロイの仕組みが実装されています。
Lumeなどの静的サイトジェネレータで生成されたページをDeno Deployにデプロイしたいときなどに活用するとよさそうです。
エコシステム
Fresh v1
PreactとDenoをベースとしたフレームワークであるFreshのv1がリリースされました。
このリリースに合わせて、FreshはDeno公式のフレームワークに変わり、Denoの公式サイトであるdeno.landなどでも活用されています。
Fresh v1に関する詳細については、以下の記事などを参照いただければと思います。
PrismaがDenoをサポート
Prisma v4.5.0にてDenoがサポートされました。
Prisma CLIやPrisma ClientをDenoから利用することができます。
$ deno run -A npm:prisma@4.8.0 init
公式ガイドも公開されているため、詳細についてはそちらを参照いただければと思います。
RemixでDenoの正式サポートが決定
Remix v1.5.0にてDenoの正式サポートが決定されました。
@remix-run/deno
パッケージを利用することで、Remixで開発されたアプリをDenoやDeno Deployなどで動かすことができます。
tea
Homebrewの作者によるDenoで実装された新しいパッケージマネージャが公開されています。
まだv1はリリースされていないものの、パッケージのインストールやpyenvなどのような仮想環境の管理など、様々な機能が実装されているようです。
コミュニティ
Deno by example が公開
Deno公式で Deno by example というサイトが公開されました。
このサイトではDenoに関する様々なサンプルコードなどが公開されています。
Deno Newsの更新が再開
Deno NewsというDenoに関する最新情報を発信しているWebサイトがあります。
このサイトはしばらく更新が停止されていたのですが、今年から改めて投稿が再開されています。
RSSフィードなども提供されているため、もし興味がありましたらウォッチしておくとよいかもしれません。
WinterCG
WinterCGというコミュニティグループが立ち上げられました。
DenoやNode.js、Cloudflare Workersなどの様々なサーバサイドJavaScriptランタイム間の相互運用性の向上を目的としているようです。
WinterCGにおける議論やWebサイトのソースコードなどはGitHub上で公開されています。
Supabase Functions
SupabaseからSupabase FunctionsというFaaSが発表されました。
Supabase FunctionsはDeno Deployをベースにしており、TypeScriptなどを利用してコードを記述できるようです。
Netlify Edge Functions
Netlifyでも同様にDeno Deployベースのサービスが発表されました。
JavaScriptやTypeScriptでEdge Functionを記述し、実行することができます。
現在、RemixやAstroなど様々なフレームワークでNetlify Edge Functionsのサポートが提供されているようです。
Slackの次世代開発プラットフォームのオープンベータ版
昨年、Slackの次世代開発プラットフォームが発表されました。
このプラットフォームではSDKやCLIなどがDenoで書かれており、Denoを使って開発ができます。
今年はこの開発プラットフォームのオープンベータ版が公開されました。
実際にこのオープンベータ版に関する記事がいくつか公開されているため、もしご興味ありましたら参照いただければと思います。
おわりに
今年はDeno本体でいくつかの大きなアップデートが入りました。
- npmパッケージのサポート
- デフォルトでの型チェックの無効化
- 設定ファイルの自動読み込み
特にこれらの機能は長い間議論などが行われており、今年ようやく導入される運びとなりました。
また、RemixやPrismaなどの著名なnpmパッケージでDenoがサポートされるなど、既存のエコシステムに関しても大きな動きが見られました。
SupabaseやNetlifyなどのプラットフォームであったり、OSSではありますがteaの実装にDenoが採用されるなど、少しずつではありますが使用事例も増えてきているようです。
正直なところ、Denoが登場した当初はここまで成長するとは予想もしていなかったため、とても驚いています。
今後、Node.js互換性やパフォーマンス・生産性の向上などが進んでいくことで、さらに使い勝手が上がっていくのではないかと感じています。
以上ですが、長文にも関わらず最後までお読みいただきありがとうございました!
Discussion