🎅

2022年のDenoの変更点やできごとのまとめ

2022/12/25に公開

Denoアドベントカレンダー 25日目の記事です🎅

https://qiita.com/advent-calendar/2022/deno

この記事では、2022年にDenoに関して起きた出来事や大きな変更点などをまとめます。

Denoの今後の方針について

今年の8月にDeno公式から今後の開発方針などが発表されました。

https://deno.com/blog/changes

具体的には、以下の点などに注力していくことが発表されています。

  • Node.jsとの互換性の向上
  • パフォーマンスの向上
  • 開発体験の向上

それぞれの詳細については後ほど紹介いたしますが、これらの方針を実現するために、Deno本体へのnpmパッケージサポート新しい高速なHTTPサーバの導入などが実施されました。

Deno本体のアップデート

npmパッケージがサポート

Denoでnpmパッケージが利用できるようになりました。

以下のように、npm:<パッケージ>@<バージョン>のような形式でimport文を記述することでnpmパッケージを利用できます。

main.ts
// @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日目の記事で紹介されています。

https://zenn.dev/kn1cht/articles/deno-textlint

型チェックの挙動に関する変更

今まで、Denoではdeno rundeno cacheなどのコマンドを実行する際に、自動でTypeScriptによる型チェックを行っていました。

しかし、型チェックは時間のかかる処理であり、場合によっては、deno runなどによるスクリプトの実行時間に影響を与えてしまいます。

また、開発時にLSPを利用している場合、コードの編集時に事前に型エラーに気づくことも少なくないはずです。

そういった背景もあり、Deno v1.23以降のバージョンからdeno rundeno cacheなどのコマンドの実行時に、DenoはデフォルトでTypeScriptの型チェックを実施しなくなりました。

もし明示的に型チェックを実施したい場合は、新しく導入されたdeno checkコマンドが利用できます。

CIなどで型チェックを実施したい場合は、このコマンドを利用するとよいと思います。

$ deno check mod.ts

タスクランナー(deno taskコマンド)が実装

Deno本体にタスクランナーが実装されました。

この機能を利用するには、deno.jsontasksにタスクを定義する必要があります。

deno.json
{
  "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相当の機能やcpmvなどのコマンドがあらかじめ組み込まれています。

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

Denoにはパーミッションシステムが組み込まれています。

これは明示的に権限を与えない限り、ネットワークやファイルシステム、環境変数などへのアクセスを禁止するための仕組みです。

例えば、以下のようにファイルを読み込みたい場合は、Denoを実行する際に--allow-readオプションを明示する必要がありました。

main.js
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によるテスト対象ファイルの設定などを行うことができます。

deno.json
{
  "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.jsonlock: falseを指定する必要があります。

deno.json
{
  "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はまだ不安定機能という位置づけであり、今後もさらなる開発が予定されているようです。

https://github.com/denoland/deno/issues/17146

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日目の記事なども参照ください。

https://qiita.com/kt3k/items/4ac4b6f21895041927aa

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本体にベンチマークの仕組みが導入されました。

sum_bench.ts
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という新しいコマンドが実装されました。

詳細については、以下の記事などを参照いただければと思います。

https://zenn.dev/uki00a/articles/an-introduction-to-deno-vendor

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.ReaderDeno.WriterなどのGoに影響を受けたAPIをベースに実装されていました。

しかし、Deno本体でWeb Streams APIの実装などが進んだこともあり、deno_stdの多くのモジュールがWeb Streams APIへ移行されつつあります。

それに関連し、std/encoding/json/stream.tsstd/streams/delimiter.tsstd/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における主流となる可能性もありそうです。

https://github.com/denoland/deno/issues/9795

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エッジ環境で実行するためのホスティングサービスです。

https://deno.com/deploy

Deno DeployのBeta 4がリリース

Deno DeployでBeta 4がリリースされました。

https://deno.com/blog/deploy-beta4

このリリースでは、有料プランであるProプランが公開されています。

各プランについては、以下の記事でまとめられているため、詳しくはそちらを参照いただければと思います。

https://qiita.com/access3151fq/items/74597aedbb601d0d2fbd

また、Deno Deployの管理画面もリニューアルされており、メトリクスの可視化やログの改善など使い勝手が向上されています。

deployctl v1

Deno Deploy公式のCLIツールであるdeployctlのv1がリリースされました。

deployctl deployコマンドやGitHub Actionによる自動デプロイの仕組みが実装されています。

Lumeなどの静的サイトジェネレータで生成されたページをDeno Deployにデプロイしたいときなどに活用するとよさそうです。

https://deno.com/blog/build-a-static-site-with-lume

エコシステム

Fresh v1

PreactとDenoをベースとしたフレームワークであるFreshのv1がリリースされました。

このリリースに合わせて、FreshはDeno公式のフレームワークに変わり、Denoの公式サイトであるdeno.landなどでも活用されています。

https://github.com/denoland/fresh

Fresh v1に関する詳細については、以下の記事などを参照いただければと思います。

https://deno.com/blog/fresh-is-stable

https://zenn.dev/uki00a/articles/frontend-development-in-deno-2022-autumn#fresh-v1.0

PrismaがDenoをサポート

Prisma v4.5.0にてDenoがサポートされました。

https://github.com/prisma/prisma/releases/tag/4.5.0

Prisma CLIやPrisma ClientをDenoから利用することができます。

$ deno run -A npm:prisma@4.8.0 init

公式ガイドも公開されているため、詳細についてはそちらを参照いただければと思います。

https://www.prisma.io/docs/guides/deployment/deployment-guides/deploying-to-deno-deploy

RemixでDenoの正式サポートが決定

Remix v1.5.0にてDenoの正式サポートが決定されました。

https://github.com/remix-run/remix/releases/tag/v1.5.0

@remix-run/denoパッケージを利用することで、Remixで開発されたアプリをDenoやDeno Deployなどで動かすことができます。

tea

Homebrewの作者によるDenoで実装された新しいパッケージマネージャが公開されています。

https://twitter.com/Linda_pp/status/1594487254281379840

まだv1はリリースされていないものの、パッケージのインストールやpyenvなどのような仮想環境の管理など、様々な機能が実装されているようです。

https://github.com/teaxyz/cli

コミュニティ

Deno by example が公開

Deno公式で Deno by example というサイトが公開されました。

https://examples.deno.land/

このサイトではDenoに関する様々なサンプルコードなどが公開されています。

Deno Newsの更新が再開

Deno NewsというDenoに関する最新情報を発信しているWebサイトがあります。

このサイトはしばらく更新が停止されていたのですが、今年から改めて投稿が再開されています。

https://deno.news/archive/42-deno-news-is-back-featuring-v121-netlify-edge

RSSフィードなども提供されているため、もし興味がありましたらウォッチしておくとよいかもしれません。

https://deno.news/

WinterCG

WinterCGというコミュニティグループが立ち上げられました。

https://wintercg.org/

DenoやNode.js、Cloudflare Workersなどの様々なサーバサイドJavaScriptランタイム間の相互運用性の向上を目的としているようです。

WinterCGにおける議論やWebサイトのソースコードなどはGitHub上で公開されています。

https://github.com/wintercg

Supabase Functions

SupabaseからSupabase FunctionsというFaaSが発表されました。

https://deno.com/blog/supabase-functions-on-deno-deploy

Supabase FunctionsはDeno Deployをベースにしており、TypeScriptなどを利用してコードを記述できるようです。

https://www.ccbaxy.xyz/blog/2022/04/16/js45/

Netlify Edge Functions

Netlifyでも同様にDeno Deployベースのサービスが発表されました。

https://deno.com/blog/netlify-edge-functions-on-deno-deploy

JavaScriptやTypeScriptでEdge Functionを記述し、実行することができます。

現在、RemixAstroなど様々なフレームワークでNetlify Edge Functionsのサポートが提供されているようです。

https://developer.mamezou-tech.com/blogs/2022/07/23/try-netlify-edge-functions/

Slackの次世代開発プラットフォームのオープンベータ版

昨年、Slackの次世代開発プラットフォームが発表されました。

このプラットフォームではSDKやCLIなどがDenoで書かれており、Denoを使って開発ができます。

https://deno.com/blog/slack

今年はこの開発プラットフォームのオープンベータ版が公開されました。

https://deno.com/blog/slack-open-beta

実際にこのオープンベータ版に関する記事がいくつか公開されているため、もしご興味ありましたら参照いただければと思います。

https://developer.mamezou-tech.com/blogs/2022/09/27/slack-new-plotform-powered-by-deno/

https://tech.travelbook.co.jp/posts/slack-deno-api/

おわりに

今年はDeno本体でいくつかの大きなアップデートが入りました。

  • npmパッケージのサポート
  • デフォルトでの型チェックの無効化
  • 設定ファイルの自動読み込み

特にこれらの機能は長い間議論などが行われており、今年ようやく導入される運びとなりました。

また、RemixやPrismaなどの著名なnpmパッケージでDenoがサポートされるなど、既存のエコシステムに関しても大きな動きが見られました。

SupabaseやNetlifyなどのプラットフォームであったり、OSSではありますがteaの実装にDenoが採用されるなど、少しずつではありますが使用事例も増えてきているようです。

正直なところ、Denoが登場した当初はここまで成長するとは予想もしていなかったため、とても驚いています。

今後、Node.js互換性やパフォーマンス・生産性の向上などが進んでいくことで、さらに使い勝手が上がっていくのではないかと感じています。

以上ですが、長文にも関わらず最後までお読みいただきありがとうございました!

Discussion