deno.json の隠し設定 "patch" についての話
はじめに
Deno 2.0 がリリースされましたね。はい。さて、それと関係ない話で恐縮ではございますが、みなさん、deno.json
には patch
という設定ができることをご存知でしょうか。
この patch
というものは、Deno の公式ドキュメントにも記載のない、言わば隠し設定のようなものとして存在します。しかし!これはある特定のユースケースにおきまして、とんでもなく便利、というよりもこいつがいないとやってられないレベルのすごい働きをしてくれるものなのです。
ということでございまして、今回はぜひこの patch
について紹介させていただきたいと思います。
patch の設定方法
deno.json
において patch
は以下のように設定します:
{
// ...
"patch": [
"../some-package-or-workspace"
],
// ...
}
もしかしたら、勘の良い方はこれだけでピンと来たかもしれませんが、おそらくほとんどの知らない方には何のことやら…さっぱりでござるという感じだろうと思います。
これから、この patch
が何をしてくれるのかを説明しますが、それには、先ほど申し上げた「patch
が活躍する特定のユースケース」というものについて具体的に触れてまいりまして、そして、そこで直面する課題をまず説明する流れとさせていただきます。ネタバレになって恐れ入りますが、もちろん、その後にこの patch
によってその課題がドカンと解決される、そういったお話の展開になる予定です。
ユースケースその1: あなたがモジュールの開発者である場合
あなたはモジュールの開発者です。
例えば、ロギングを行うためのライブラリモジュールを作る場面などを想像してみましょう。普段は @std/log
を使ってロギングを行っていたのですが、少し標準では難しいカスタマイズをしたくなりました。例えば「ログを出力するたびに背中が痒くなるロガーがあったら面白いんじゃないか」と考えて、@std/log
を薄くラップして背中を痒くする機能を入れるような場面です。作ったモジュールは JSR に公開する予定です。
きっとあなたは近い将来、背中を痒くする機能が実現不可能であることを知り、プログラミングの限界を思い知ることになるでしょう。しかし、それは置いておいて、その前に直面する困難がある可能性が高いです。ここではそれをまず説明します。
ローカル開発環境でのモジュールの動作確認
モジュールを公開するときに、いきなり空っぽの状態で JSR に publish する人はいないでしょう。いや、そういう豪胆な人もいるかもしれませんし、名前キープでそういうことをやる場合もあるかもしれませんが、通常はまずある程度のアルファ版程度の機能は開発するはずです。
そのとき、必ず動作確認も行うのが当然です。今回のようにロガーライブラリを実装する場合などはわかりやすい例ですが、実際には他のアプリケーションに組み込んで利用されることを想定しているので、動作検証でも「テスト用のサンプルアプリケーションに組み込んで動かす」ということをやることが多いでしょう。
モジュールの動作確認における deno.json の imports 問題
上記したような形でモジュールの動作確認を行う場合、以下のようなディレクトリ構成が典型的かと思います:
.
├── example-app
│ ├── deno.json
│ ├── main.ts
│ └── main_test.ts
└── your-module
├── deno.json
├── mod.ts
└── mod_test.ts
-
example-app
: モジュールを利用するサンプルアプリケーション -
your-module
: あなたが開発しているモジュール (deno init --lib
で生成したもの)
your-module
は @std/log
をラップしたロギング用のライブラリという設定なので、your-module/deno.json
はこんな感じになっているとします:
your-module/deno.json
{
// ...
"imports": {
"@std/log": "jsr:@std/log@^0.224.9"
}
// ...
}
そして、example-app
でこの your-module
を利用しながら確認したいので、そのために import する場合には、example-app/deno.json
に以下のように記述することになると思います:
example-app/deno.json
{
// ...
"imports": {
"your-module": "../your-module"
}
// ...
}
そして、example-app
に your-module
が export しているロギング用の API を import して、アプリケーション内でそれを使う、という形での動作確認を行うというのが自然な流れです。
しかし、実はこれはうまくいきません。example-app
の実行時に、例えば以下のようなエラーが発生します。
error: Relative import path "@std/log" not prefixed with / or ./ or ../ and not in import map
これがなぜかというと、ローカルに設置したモジュールから import する場合、Deno はエントリポイントとなるアプリケーション (今回は example-app
) の import maps しか読み込んでくれないためです。
こういう事情があるので、Zenn Book の Effective Deno では、
deps.tsとImport mapsのどちらを使えばいいの?
- 自分でサードパーティモジュールを書くときはdeps.tsで管理する
と書かれています。
困りました。
しかし、はい、そうです。この問題を解決するのが deno.json
の patch
です。
patch による解決策
patch
は、imports
に指定されたパスを指定されたパスに置き換えて、かつ import maps をそれぞれのモジュールの依存として期待通りに解決できるようにしてくれる設定です。
つまり、import する側である example-app
の deno.json
を以下のように設定することで、この問題は簡単に解決します:
example-app/deno.json
{
// ...
"imports": {
"your-module": "../your-module/mod.ts"
},
"patch": ["../your-module"]
// ...
}
実際に example-app
を実行すると、先ほどの @std/log
が解決できないというエラーが消え、めでたくあなたのモジュールの動作確認ができるようになりました!これで Deno のモジュール開発が何倍にも捗ることでしょう。
ユースケースその2: インストールした依存モジュールにパッチを適用したい
patch
の恩恵はモジュール開発者のみに留まるものではありません。
アプリケーションの開発では、サードパーティのモジュールに依存することが当然になっています。しかし、そのモジュールが
- 期待通りに動いていない
- アップデートしたら動かなくなった
- 何もしてないのに壊れた
こういった場面に直面することもあるでしょう。そんなとき、手元でそのモジュールにアドホックにパッチを当てたり、トラブルシューティング用にログを追加したり、ときにはそのモジュールの Git リポジトリを clone してきて git bisect
で原因を特定したいケースもあるかもしれません。
patch
を使うとこのようなケースでもとても便利です。むしろ、patch
という名前の設定ですし、こっちのユースケースの方がメインでしょう。
インストールした依存モジュールにパッチを適用する
使い方は「ユースケースその1」で書いたものとほぼ同じなので簡単にいきます。
あなたのアプリケーションの deno.json
に patch
を追加し、その中にパッチを適用したいモジュールのパス (ダウンロードしてきたモジュールの格納されているディレクトリ) を指定するだけです。
your-app/deno.json
{
// ...
"imports": {
"some-module": "jsr:@someone/some-module@^1.0.0"
},
"patch": ["/path/to/some-module"]
// ...
このとき、imports.some-module
はそのまま jsr:*
になっていて書き直す必要はないことに注目してください。imports
は書き換えずに patch
に追加するだけで期待した通りにモジュールの import map を解決する動作になってくれます。すごく使いやすいですよね。
patch の安定版のリリースに向けて
最初に書いたように、deno.json
の patch
はまだ公式にアナウンスされた機能ではありません。
ただ、ここで書いてきたように求められるユースケースはあるので、Deno 1.46 でフィードバックを集めるための実験的な機能として導入されました。
そして、この issue で実際にそのフィードバックを集めています。
自分もサムアップをポチっただけなんですが、まだあまり積極的なフィードバックは寄せられていないようです。
ただ、この機能はデベロッパーフレンドリーな Deno のためには欠かせないものだと思っているので、ぜひ皆さんにも知ってもらい使ってもらって、そしてフィードバックを寄せてもらい、早く安定版として正式リリースされると良いな、というのがこの記事を書いた理由でした。
Discussion