🦕

deno.json の隠し設定 "patch" についての話

2024/10/11に公開

はじめに

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-appyour-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で管理する

と書かれています。

https://zenn.dev/uki00a/books/effective-deno/viewer/manage-deps-in-a-centralized-way#deps.tsとimport-mapsのどちらを使えばいいの%3F

困りました。

しかし、はい、そうです。この問題を解決するのが deno.jsonpatch です

patch による解決策

patch は、imports に指定されたパスを指定されたパスに置き換えて、かつ import maps をそれぞれのモジュールの依存として期待通りに解決できるようにしてくれる設定です。

つまり、import する側である example-appdeno.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.jsonpatch を追加し、その中にパッチを適用したいモジュールのパス (ダウンロードしてきたモジュールの格納されているディレクトリ) を指定するだけです。

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.jsonpatch はまだ公式にアナウンスされた機能ではありません。

ただ、ここで書いてきたように求められるユースケースはあるので、Deno 1.46 でフィードバックを集めるための実験的な機能として導入されました。

https://github.com/denoland/deno/pull/25068

そして、この issue で実際にそのフィードバックを集めています。

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

自分もサムアップをポチっただけなんですが、まだあまり積極的なフィードバックは寄せられていないようです。

ただ、この機能はデベロッパーフレンドリーな Deno のためには欠かせないものだと思っているので、ぜひ皆さんにも知ってもらい使ってもらって、そしてフィードバックを寄せてもらい、早く安定版として正式リリースされると良いな、というのがこの記事を書いた理由でした。

Discussion