text.hmsk.me

vite-plugin-elm でアセットのインポートをサポートした

April 12, 2022

code

Issue でリクエストされていて、確かにあるといいよねと思いつつ、しっくり来る実装をしばらく考えながらベータを重ねてようやく出した。欲しがっていたっぽい人たちは音沙汰が無くなり、少し悲しいと思いながらも、準備されていたら嬉しそうな機能を持てたのは良かった。

Elm のコンパイルと JS バンドラー

Vite が JS のソースに合わせてアセットのインポートの世話をする性質上、Elm のコンパイル時点では何か手出しできることがなく、Elm の出力をよしなに書き換えて Vite のインポートに乗っかる他ない。Elm では [ASSET:/path/to/asset] みたいな取り決められた文字列を持っておくようにして、プラグインがそれを Vite に合わせた変数に置き換えつつ、求められたアセットの import 文を差し込むようにしている。

Webpack 向けにはいくらか先行実装が存在しているのを見つけたが、いずれも同様のアプローチを取っているようだった。

序盤のベータでは、正規表現でゴニョっと処理するところから始めて、後述するヘルパーの作成を想定したあたりで、パーサー、AST ジェネレータがあった方が良さそうに見えてきた。依存関係を足すのは億劫で、当初は esbuild が Vite で使われているので、それを使えればと思っていたら、JS 向けに AST を取り出すような API が公開されておらず(破壊的な変更を内部的にじゃんじゃんやれるように外には出さない方針らしい)しばらく悩んだ上で追加の依存関係を持たない acorn と生成された AST を歩いてくれる acorn-walk を使うことにした。

このアプローチは、Elm をプロダクションでじゃんじゃん使っている NoRedInk が公開しているパッケージ NoRedInk/elm-assets-loader (もう使わなくなったようでアーカイブされている)でも取られていた。

最終的には [VITE_PLUGIN_ELM_ASSET:/assets/logo.jpg] と Elm のコードに単独の文字列として書いて <img> タグの src アトリビュートに使っておくと、

import _assets_logo_jpg from '/assets/logo.jpg'

// Elm のコンパイルされたコード
// ...
var srcAttr = _assets_logo_jpg
// ...

みたいに置き換えられるようになって、要求を満たした。ただ、この形の場合、コンパイル後のコードが Vite に読み込まれた時点で全ての読み込み予定のアセットを取り込むことになるので、初期時点では表示しないアセットを無用に読み込む羽目になる。

これを Vite で回避するには import('/assets/logo.jpg') をインラインで持つようにすればいいのだけど、Elm のコンパイルされたコードは基本的に async/await を持たないコードになるので、この文から返された Promise<string> を簡単に取り扱うことができない。これをサポートするのに、もっと Elm のコンパイル後のコードに手を入れる必要が出てくる(かなりの大手術になりそうに見える)ので今回は見送った。Webpack の場合、インラインで同期的に読み込む形で require('/assets/logo.jpg') と書けるっぽいが、Vite でもそれをサポートするようなプラグインに乗ることでうまくいくかもしれないが未調査。

ヘルパー

いちいち、[VITE_PLUGIN_ELM_ASSET:/assets/logo.jpg] みたいなのを書くのは面倒だけど、短くしすぎて意味がわからないとか、他と無用に衝突し得る文字列になるのも嫌なので、この長めのタグを残す代わりに、Elm 向けに VitePluginHelper.asset "/assets/logo.png" と書けば済むようなパッケージを提供することにした。

import VitePluginHelper

Html.img [ Html.Attributes.src <| VitePluginHelper.asset "/assets/logo.png?inline" ] []

みたいに書くことができる。現状の実装では、インラインで文字列が直接渡されることが必須になっている。ヘルパーが提供する関数をコンパイル後のコードから特定して、呼び出し側に直接インポート対象を渡すような形に書き換えている。動的なインポートが叶うようになったら、もっと自由に変数や関数を渡すことができそう。