Deno v2に向けて - Deno v2, deno_std v1, Fresh v2について
Deno v1がリリースされてから4年程が経過しました。
そろそろDeno v2はいつごろ出るんだろう?と疑問に思っている方もいらっしゃるかもしれません。
この記事ではDeno v2やその周辺などに関して、現状、どのような対応が進んでいるのかなどについてまとめます。
Deno v2について
Deno v2についてなのですが、リリース時期についてはちょっとまだわからない状況です。
ただ、現在の状況として、Deno v2のリリースに向けた対応は少しずつ進められてる様子が見られます。
具体的に現在、どういった変更が計画または進められているのかについて見ていきたいと思います。
Node.js互換性の改善
以前にいくつか記事にもしましたが、Node.js互換性の改善は引き続きかなり力を入れて進められています。現状では以下のような機能などが実装されています。
npm:
URLによるnpmパッケージのimport
node_modules
の作成package.json
のサポート- BYONM
-
.npmrc
のサポート (Deno v1.44)
Node.js互換性に力が入れられている背景について
背景の一つとして依存関係の重複問題を解決したいという目的があります。
まずDenoの大きな特徴の一つとして、任意のURLからのモジュールのimport
がサポートされています。この機能を活用するために、Deno公式からdeno.land/xというレジストリが提供されており、現状、Deno向けの多くのライブラリはこのdeno.land/xで公開されています。
以下はdeno.land/xからライブラリを利用する例です。
import { connect } from "https://deno.land/x/[email protected]/mod.ts";
const redis = await connect({
hostname: "127.0.0.1",
port: 6379,
});
この機能はシンプルでわかりやすく特にスクリプティングなどにおいては便利であるものの、以下の公式ブログでも解説されているように、大規模なアプリケーションなどにおいては依存関係の重複が発生し得るというデメリットがあります。
具体的にはstd/uuid/v1.ts
に依存したfoo
とbar
という2つのパッケージがあったとします。パッケージfoo
はstd/uuid/v1.ts
の0.223.0
, パッケージbar
はstd/uuid/v1.ts
の0.224.0
に依存しているとします。
app
├── foo
│ └── [email protected]/uuid/v1.ts
└── bar
└── [email protected]/uuid/v1.ts
パッケージfoo
とbar
それぞれが依存しているstd/uuid/v1.ts
の0.223.0
と0.224.0
は内容がまったく変わりません。
しかし、これらはそれぞれバージョンが異なるため、もしアプリケーションがfoo
とbar
の両方のパッケージに依存している場合、内容が重複したモジュール(std/uuid/v1.ts
の0.223.0
と0.224.0
)が複数インストールされてしまいます。
この問題を解消するためにはsemverに基づいた依存解決の仕組みなどが必要になります。Denoでnpmパッケージをサポートするためにはいずれにせよsemverに基づいた依存解決の仕組みが必要になるため、Denoにnpmパッケージのサポートが導入されることによって自然とこの問題に対する解決策が提供されることになります。また、npmパッケージがサポートされることにより、Node.js向けの豊富な資産をDenoでも活用できる余地が産まれます。
Node.js互換性に関する現在の状況について
Node.js互換性の改善によりDocusaurusがDenoで動作するようになりました。これに伴い、Deno公式のドキュメンテーションサイトの実装もNode.js+DocusaurusからDeno+Docusaurusへ移行されています。
また、Next.jsが動作するようになったことが直近のDeno v1.44の公式ブログで発表されています。(この件に関しては、後日、Deno公式からブログが公開される予定のようです)
ただし、Next.jsを動作させるためには、現時点では後述するDENO_FUTURE=1
の指定が必要なようです。
Node.js互換性に関するv2に向けた対応について
Deno v2向けに以下の変更などが検討されています。
- BYONMのデフォルトでの有効化
-
deno install
コマンドの振る舞いの変更 - npmやYarnなどとの相互運用性の改善
それぞれの内容について紹介していきます。
BYONMのデフォルトでの有効化
BYONMとはnpmやpnpm, Yarnなどのパッケージマネージャーによって作成されたnode_modules
ディレクトリからnpmパッケージを読み込むための機能です。
このBYONMを使うことで、npmパッケージの管理はYarnに任せつつ、DenoからはYarnによってインストールされたパッケージをそのまま利用するようなことができます。
現時点では、BYONMを利用するためにはdeno.json
などで明示的に有効化する必要があります。
Deno v2ではこの挙動が変更され、package.json
が存在する場合はデフォルトでBYONMを有効化することが計画されています。
後述するDENO_FUTURE=1
を指定することで、Deno v1でもこの挙動を試すことができます。
deno install
コマンドの振る舞いの変更
Denoにはdeno install
というコマンドがあります。このdeno install
コマンドはDeno v1においては、任意のホストで公開されたスクリプトをローカルにインストールするためのコマンドです。(Node.jsで例えるとnpm install -g
, Goで例えるとgo install
相当の振る舞いをするようなイメージです)
$ deno install -rf --allow-read=. --allow-write=. --allow-net https://deno.land/x/[email protected]/main.ts
⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
✅ Successfully installed udd
/path/to/.deno/bin/udd
このdeno install
コマンドの振る舞いがDeno v2で変更されることが計画されています。
まず、deno install
コマンドは依存関係をプロジェクトに追加するためのコマンドとして振る舞うように挙動が変更されます(後述するdeno add
コマンドと同様の振る舞いをします)
# 現時点でこの挙動を試すには`DENO_FUTURE=1`の指定が必要です
$ deno install npm:[email protected]
# deno.jsonにchalkに関するマッピングが書き込まれます
$ cat deno.json | jq .imports.chalk
"npm:chalk@^5.3.0"
また、引数なしでdeno install
コマンドを実行した場合は、プロジェクトが依存している各パッケージをダウンロードし、ローカルにキャッシュしてくれるようです。
# 現時点でこの挙動を試すには`DENO_FUTURE=1 `の指定が必要です
$ deno install
このように、deno install
コマンドはnpm install
やyarn add
などと同様の振る舞いをするように挙動が変更される想定です。
もしdeno install
コマンドにDeno v1と同じような振る舞いをして欲しい場合には、明示的に--global
というオプションを指定する必要があります。
$ deno install --global -rf --allow-read=. --allow-write=. --allow-net https://deno.land/x/[email protected]/main.ts
npmやYarnなどとの相互運用性の改善
Denoは独自にロックファイルの仕組みを備えています。(deno.lock
)
直近でリリースされたDeno v1.44ではpackage.json
が存在する場合に、自動でロックファイルを生成する仕組みも導入されています。
しかし、既存のNode.jsアプリケーションでDenoを利用する上では、deno.lock
ではなくYarnなどで生成されたロックファイルをそのまま使いたいというケースもあると思います。こういった要求を満たすため、前述のdeno install
コマンドでnpmやYarnなどが生成したロックファイルを認識できるようにすることがDeno v2で検討されているようです。
Node.js互換性に関するまとめ
Deno v2に向けては、主に既存のパッケージマネージャーとの相互運用性の改善や使用感の統一などが想定されているようです。おそらく、既存のNode.jsプロジェクトにおいて、ソースコードを変更せずにDenoを使えるようにすることなどが想定されているのだと思われます。
今後、Deno公式からNext.jsの利用に関する記事が公開されることが計画されているようなので、そちらでも色々と発表される可能性があるのではないかと思います。
JSR
JSRというパッケージレジストリがDeno公式で公開されました。
このJSRの特徴として、TypeScriptのネイティブサポートやドキュメンテーションの自動生成などの機能を備えています。
また、npmレジストリとも互換性があり、以下のツールを利用することでNode.jsやBunなどのランタイムでもJSRで公開されたパッケージを利用することができます。
このJSRの公開に合わせて、DenoにJSRのネイティブサポートが追加されました。
DenoでJSRパッケージを利用する方法
jsr:
URL
DenoからJSRで公開されたパッケージを利用するには、jsr:@<scope>/<package>@<version>
という形式のURLを指定します。
例えば、以下は@davidというスコープで公開されているdaxパッケージのv0.41.0を利用する例です。
import $ from "jsr:@david/[email protected]";
$.log("Hello Deno!");
このようにJSRではパッケージの公開にあたって必ずスコープの指定が必要になるのが特徴です。jsr:
経由で指定されたJSRパッケージは、通常のhttps:
形式のパッケージなどと同様に、Denoを実行する際に自動でダウンロードされてキャッシュされます。
deno add
コマンド
また、deno add
というコマンドも追加されています。このコマンドを使うと、Denoが指定されたJSRパッケージをdeno.json
のimports
に書き込んでくれます。
$ deno add @david/dax
Add @david/dax - jsr:@david/dax@^0.41.0
$ cat deno.json | jq '.imports."@david/dax"'
"jsr:@david/dax@^0.41.0"
これにより、以下のようにして@david/dax
パッケージを読み込むことができます。
import $ from "@david/dax";
$.log("Hello Deno!");
また、npmパッケージの追加も可能です。
$ deno add npm:chalk@^5
Add chalk - npm:chalk@^5.3.0
$ cat deno.json | jq .imports.chalk
"npm:chalk@^5.3.0"
パッケージの公開 (deno publish
)
Deno本体からJSRにパッケージを公開するためにdeno publish
というコマンドが提供されています。このdeno publish
ではドライランもサポートされているので、パッケージを公開したい場合はまずこれを試してみるとよいと思います。
$ deno publish --dry-run
パッケージの公開に関しては以下のページなどで詳しく説明されているため、よろしければそちらなども参照ください。
fast check
JSRに関連した独自の仕組みとしてfast checkという機能がDenoに組み込まれています。
fast checkとは、slow typesと呼ばれるTypeScriptにおける型チェックの低速化の要因になりうる定義を検出してくれる仕組みです。具体的には、パッケージの公開APIのうち、明示的に型定義が記述されていないような関数などを検出してくれます。
// Bad - 戻り値の型定義が省略されている
export function add(a: number, b: number) {
return a + b;
}
// Good - 引数と戻り値の型がきちんと定義されている
export function add(a: number, b: number): number {
return a + b;
}
このslow typesが存在するパッケージについては、そうでないパッケージと比べて、型チェックをする際に時間がかかってしまう可能性があります。
Denoはdeno publish
などのコマンドを実行するときにfast checkを実行することで、JSRパッケージの利用者がslow typesによって体験を低下してしまわないようにすることが意識されています。
このfast checkはJSRパッケージの作者向けの機能です。そのため、ユーザーとしてJSRパッケージを利用する分には特に気にしなくても問題ありません。
このslow typesの詳細については以下のドキュメントなども参照ください。
JSRが導入された背景
jsr:
URLは元々はdeno:
URLとしての導入が想定されていた機能だと思われます。
元々、deno:
URLが導入されようとしていた背景は、前述の依存関係の重複問題を解消することが目的でした。
npmパッケージをDenoがサポートすることにより、依存関係の重複問題は部分的に解消されます。npmパッケージを利用する場合は、Denoがsemverに基づいて依存解決を行ってくれるためです。
しかし、DenoやDeno Deploy向けのアプリケーションを開発する場合は、npmパッケージではなく、DenoやDeno Deploy向けに専用に開発されたライブラリ(例: Fresh, deno_stdなど)を使いたいというケースも出てくるかと思います。この場合は、deno.land/xを利用することになるため、結局、前述の依存関係の重複問題が発生してしまいます。
これを解消するためにはDeno向けのパッケージに対してもsemverに基づいた依存解決の仕組みが必要になります。
この問題の解決策の一つとして、Deno向けのライブラリをnpmレジストリに公開するという手段がありそうです。しかし、Deno向けのライブラリをnpmレジストリに公開するとなると、今度はそのパッケージをNode.jsなどからも利用したいという要望が発生する可能性が高いと思われます。その場合は、Node.jsからはTypeScriptコードを直接実行することはできないため、パッケージの公開前に自前でTypeScriptのコードをJavaScriptにトランスパイルしてから公開するなどの手間が発生してしまいます。
JSRではレジストリがこういった作業などを肩代わりしてくれることで、パッケージ公開に関する手間を軽減してくれます。
まとめると、以下の課題を解消することなどを主な目的としてJSRは開発されたものなのだと思われます。
- Deno向けのパッケージにおいても依存関係の重複問題を解消したい
- パッケージの公開に関する体験を改善したい
このJSRにはすでにHonoやOakなどの著名なパッケージも公開されており、今後、Deno向けのライブラリを利用する場合は、JSRを利用することがメジャーになる可能性が高いのではないかと思います。
ワークスペース機能の導入
Denoにワークスペース機能が導入されます。ワークスペースを利用する際は、まずdeno.json
でworkspaces
を定義する必要があります。
{
"imports": {
"$dax": "jsr:@david/[email protected]"
},
"workspaces": [
"foo",
"bar"
]
}
上記ではfoo
というbar
という2つのワークスペースが定義されています。ディレクトリ構造としては以下のように各ワークスペースごとにディレクトリを用意する必要があります。
.
├── deno.json
├── mod.ts
├── bar
│ ├── deno.json
│ └── mod.ts
└── foo
├── deno.json
└── mod.ts
このように、それぞれのワークスペースごとにdeno.json
を配置することができます。
以下はbar
ワークスペースのdeno.json
の定義で、このようにname
やversion
, exports
などを定義する必要があります。
{
"name": "@test/bar",
"version": "0.0.1",
"exports": {
".": "./mod.ts"
},
"imports": {
"chalk": "npm:[email protected]"
}
}
bar
ワークスペース内のモジュールではbar/deno.json
とルートのdeno.json
に基づいて依存解決が行われます。
// `[email protected]`が読み込まれます
export { default as chalk } from "chalk";
// ルートディレクトリで定義された`@david/[email protected]`が読み込まれます
export { default as $ } from "$dax";
foo
ワークスペースについても同様です。こちらではbar
ワークスペースとは異なるバージョンのchalk
を読み込んでいます。
{
"name": "@test/foo",
"version": "0.0.1",
"exports": {
".": "./mod.ts"
},
"imports": {
"chalk": "npm:[email protected]"
}
}
// `[email protected]`が読み込まれます
export { default as chalk } from "chalk";
以下はプロジェクトのルートディレクトリに配置されたmod.ts
の例で、それぞれのワークスペースをパッケージとして読み込むことができます。
// fooを読み込みます
import { chalk as chalk_foo } from "@test/foo";
// barを読み込みます
import { chalk as chalk_bar, $ } from "@test/bar";
console.info(chalk_foo.red("foo"));
console.info(chalk_bar.red("bar"));
// fooでは`[email protected]`, barでは`[email protected]`が利用されているため、falseになります
console.assert(chalk_foo.red !== chalk_bar.red);
$.log(chalk_foo.green("Hello bar"));
今まで、Denoではプロジェクトごとに一つしかImport mapsを定義できない課題がありました。ワークスペース機能の導入により、この課題の解消が期待されます。
このワークスペース機能はすでにDeno本体に実装されていますが、Deno v2に向けていくつかの改善がまだ残っているようです。具体的にはnpm workspaceのサポートなどが検討されているようです。詳しい進捗などについては以下のissueなどを参照ください。
非推奨APIの削除
Deno本体の非推奨APIが削除される想定です。対象APIについては以下のマイグレーションガイドで解説されています。
大きなものとしては、Deno.Reader
やDeno.Writer
が削除される予定です。
これらはDenoの初期の頃に、Goのio.Reader
やio.Writer
などに影響を受けて導入されました。Deno v1のリリース以降、DenoではWeb APIの利用が重要視されるようになったこともあり、IOに関する機能などもDeno.Reader
やDeno.Writer
ではなくWeb Streams APIをベースに実装されるケースが増えてきました。
また、以下のissueでも説明されていますが、現在のDenoにおいてはシステムコールに依存しない機能については、できる限りDeno.*
配下に置くべきではないという方針が取られているようです。
こういった背景などもあり、Deno本体からはDeno.Reader
やDeno.Writer
などの型が削除されることになりました。(注意点として、Deno.Reader
やDeno.Writer
については型が削除されるだけであって、Deno.FsFile
などのAPIからは依然としてread
やwrite
などのメソッドが提供されます)
これらの型は@std/io/typesに移植されているため、今後はこちらから利用するとよさそうです。
非推奨APIの安定化
今まで非推奨APIとして提供されていたいくつかの機能がv2で安定化される可能性が高そうです。大きなものとしては、以下の機能などが安定化される想定のようです。
- FFI (
Deno.dlopen
など) - WebGPU API
DENO_FUTURE
環境変数の導入
DENO_FUTURE
という環境変数が導入されています。この環境変数を指定することで、Deno で将来的に実施予定の破壊的変更などを先んじて試すことができます。例えば、DENO_FUTURE
を利用することで、非推奨化されており削除予定のAPIを無効化することができます。
$ DENO_FUTURE=1 deno run mod.ts
この環境変数を設定しておくことで素早くv2に移行しやすくなると思われるため、もしパッケージなどを開発されている場合は、CIなどでのテスト実行時にこの環境変数を有効化しておくと便利かもしれません。
deno_std v1
Denoの公式標準パッケージであるdeno_stdのv1について解説します。
deno_std v1はいつ出るの?
結論として、deno_stdで提供されるモジュールの一つである@std/bytesではすでにv1がリリースされています。
具体的にどういった状況なのかについて説明いたします。
2024/06/11 追記
Deno公式からdeno_std
の各モジュールの安定化に関するスケジュールが公開されました。
v1が中々リリースできなかった背景
deno_std
には様々なモジュールが存在します。
例)
これらはモジュールごとに成熟度にバラツキがあります。
例えば、@std/path
は使用率が高く長期間メンテナンスもされており、比較的動作が安定していると考えられます。そのため、@std/path
については今後、破壊的変更が起きる可能性も比較的低いと思われます。
しかし、@std/expect
など、中にはまだ成熟度が高くないパッケージもあります。そのため、deno_std全体でv1をリリースしてしまうと、こういった成熟度が高くないパッケージに対して破壊的変更を入れることが難しくなってしまいます。
JSRへの移行
Deno公式からJSRというパッケージレジストリが開発されました。deno_std
もこのJSRへパッケージが公開されています。(@std)
JSRが登場したことにより、deno_std
の各モジュールごとに独立してバージョン管理をすることが可能になりました。
これにより、安定性が高いと考えられる@std/bytesについては先日、v1がリリースされました。
@std/pathや@std/collectionsなどのモジュールについてもv1のRCバージョンがすでに公開されており、近日中にv1がリリースされる可能性が高いと思われます。
逆に@std/expect
などの成熟度がまだ高くないモジュールについては、しばらくv1はリリースせずに開発が継続されていくものと思われます。
deno.land/std
はどうなるの?
deno.land/stdについては、過去のバージョンのdeno_std
は残り続けているものの、JSRへの移行後のバージョンについては公開されていません。
そのため、今後はdeno.land/stdではなくJSRからdeno_std
を利用することが推奨されます。
$ deno add @std/path
Fresh v2
FreshはPreactとesbuildをベースにしたDeno公式のWebフレームワークです。
Fresh v2の開発状況について
元々、Fresh v2向けのコードは独立したブランチで開発が行われていたのですが、先月、main
ブランチにマージされました。
現在は、Fresh v2のアルファバージョンがJSRで公開されており、近いタイミングでFresh v2がリリースされる可能性があるかもしれません。
Fresh v2のRoadmapについては以下のページで公開されています。
また、Freshのリポジトリのwww
ディレクトリのコードを見てみるとイメージがしやすいかもしれません。
主な変更内容
内容が多くなってしまいそうなので、主要そうなものについてのみ抜粋します。以下のページにも変更点を少しずつまとめていっているため、もし興味がありましたら参照いただければと思います。
JSRへの公開
Fresh関連の各種パッケージがJSRへ公開されています。(@fresh)
この変更に合わせて、Preactやesbuildなどのパッケージがesm.sh経由ではなくnpm:
経由(npmレジストリ)で読み込まれるように変更されています。この変更により、Freshのプロジェクトでdeno.lock
が利用しやすくなりそうです。
ExpressライクなAPIの提供
以下のように命令的にルーティングやIslandなどの設定をするためのAPIが追加されるようです。これにより、Freshのプラグインからより柔軟に設定をカスタマイズすることができるようになりそうです。
import { App, fsRoutes, trailingSlashes } from "@fresh/core";
const app = new App({ root: import.meta.url })
// ミドルウェアの設定
.use(trailingSlashes("never"));
// ルーティングの設定
await fsRoutes(app, {
loadIsland: (path) => import(`./islands/${path}`),
loadRoute: (path) => import(`./routes/${path}`),
});
export { app };
createDefine()
API
FreshにおけるHandlerやページコンポーネントをより定義しやすくするよう、createDefine()
というAPIが提供されるようです。
import { createDefine, page } from "fresh";
interface State {
title: string;
}
const define = createDefine<State>();
export const handler = define.handlers({
async GET(ctx) {
const { title, content } = await readPost(ctx);
ctx.title = title;
return page({ content });
},
});
export default define.page<typeof handler>(function PostPage(props) {
return (
<>
<article>
{props.data.content}
</article>
</>
);
});
@fresh/core/compat
Fresh v1との互換性のためのレイヤーが存在するようです。
Fresh v1を使ったプロジェクトでFresh v2のアルファバージョンを試してみたい場合に活用すると便利かもしれません。
プロジェクト公正に関する変更
マイグレーションガイドによると以下のような変更がありそうです。
-
fresh.gen.ts
/fresh.config.ts
の削除 - 事前ビルドの利用の必須化
Fresh v1においては事前ビルドなどのプロセスが不要であることが特徴の一つでしたが、現在はFreshのホームページからもそれに関する言及が削除されています。
今後、Freshのアプリケーションを本番環境にデプロイする際は、事前ビルドは基本的に必須になる可能性が高そうに思います。
マイグレーションガイド
現時点で最新のマイグレーションガイドは以下にあります。(適宜、最新のものを参照いただければと思います)
このページでもFresh v2でどのような変更が行われる想定なのか説明されています。
おわりに
Deno v2についてはそろそろ出そうな雰囲気はありそうですが、正直ちょっとまだいつ出るかは分からない状況です。ただし、昨年あたりからマイグレーションガイドの整備が行われていたり、DENO_FUTURE
環境変数の導入なども進んでおり、Deno v2に向けた対応については着実に進んでいるようには見えます。個人的にはDeno v2に向けて開発が進められているワークスペース機能についてはかなり便利そうに感じているため、とても良さそうに見えました。
Discussion