Deno Deployで静的解析できないダイナミックインポートは、できない(が、条件付きで回避できることがある)

Deno Advent Calendar 2023 14 日目の記事です。

最近、Luciaを使ったFresh用の認証プラグイン「Plantation」を作った。

ダイナミックインポートも使って、デフォルトの使いやすさとユーザーでのカスタマイズ性を頭に入れて実装した。
紹介のためにデモサイトをDeno Deployに構築したら、ダイナミックインポートが動かなかった。

調べてみると、現在の仕様の理解と回避方法で対応できたので、まとめておきたい。

参考

現在の Deno と Deno Deploy でのダイナミックインポート

ダイナミックインポートの挙動について、Deno と Deno Deploy では、少し異なっている。

  • Deno
    • deno run
      • 動作する
    • deno compile
      • 静的解析可能なものは動作する
      • 静的解析できないものは動作しないが、–include フラグを使うことで解決できる
  • Deno Deploy
    • 静的解析可能なものは動作する
    • 静的解析できないものは動作しない

(一応この理解をしていますが、間違っていたらすみません。)

開発時は、deno run ~ で動いていたので、喜んでダイナミックインポートを使っていたが、広い意味でのDenoでは必ずしも動くと言えないケースもあった。

Deno Deploy の change log には、2023年7月から「静的解析可能な」ダイナミックインポートに対応している。

Deno Deploy’s Changelog - Statically analyzable dynamic imports

この更新情報が出たのちに、「静的解析可能」ではないダイナミックインポートも可能にしたいというフィードバックが出ていた。

github - denoland/deploy_feedback - “True” (non-statically-analyzable) dynamic imports

対策として、https://github.com/ayoreis/importを参照してくださいという応答が出ている。

これに従ったが、自分のやりたかったケースではマッチしなかった。

しかし、10月に入っているコメントで示されている方法がマッチした。
それは動的インポートする対象ファイルを、先に何もしない静的解析可能な動的インポートをしておくこと。
コメントを引用すると次のようになっています。

1
2
3
4
5
6
7
// この静的解析できないダイナミックインポートをするとき
const {mod} = await import(`./routes/${url.pathname}`) // a,ts や、b.tsが入る予定

// 次のように静的解析可能なダイナミックインポートを書いておく
(async () => await import("./routes/a.ts"));
(async () => await import("./routes/b.ts"));
(async () => await import("./routes/c.ts"));

解説によると、アナライザがデプロイメント中にインポートを解決して、後でダイナミックインポートができるそうです。
読んでいての解釈としては、静的解析可能な形でファイルのリンクを行う。
後で静的解析できないダイナミックインポートをするけど、すでにインポートされているので解決する。
という解釈をしています。

Plantation での実装

Plantationでは、ダイナミックインポートで読み込むコードは、CLIで生成させています。

https://github.com/Octo8080X/plantation/blob/main/cli.ts

調べて対策方法が分かったので、生成時に以下のような静的解析可能なダイナミックインポートのコードを合わせて生成するようにしました。

plantation/extra_loader.ts
1
2
3
4
console.log("Load ./plantation/*!");
(async () => await import("../plantation/user/create.tsx"));
(async () => await import("../plantation/user/login.tsx"));
(async () => await import("../plantation/user/logout.tsx"));

併せて、 このソースの読み込みを促す形をとるようにしました。

1
Please add `(async () => await import('./plantation/extra_loader.ts'));\` to your main.ts.

指示出している通りFreshのmain.tsで読ませると、Freshで静的解析できないダイナミックインポートが解決できます。
いくらダイナミックインポートとはいえ、Deno Deployに展開する時くらいのタイミングでは対象は確定しているので今回のケースではこれで十分です。

Issueの記述をお借りすると「真のダイナミックインポート」をDeno Deployが対応されるまではこの形式で良さそうです。

まとめ

Deno Deployでダイナミックインポートをしたいなら、静的解析可能なダイナミックインポートする「だけ」のコードを書いておく。
この対応をするとよい。

静的解析できる形に落とし込めない場合、https://github.com/ayoreis/import を見る。
これでも解決できない場合、Deno Deployの機能更新を待ってみるのが良いのでは。


以上、動作環境を考えずダイナミックインポートで機能構築したら動かなくて、対策を講じた話でした。

では。