Vue 3 ではコンポーネント単位で delimiters を変更できる

サーバーサイドのテンプレートエンジンと Vue.js が混在しているプロジェクトを経験したことがある人には馴染み深い(?) Vue の delimiters オプションですが、Vue 3 になってからコンポーネント単位で設定できるようになっていました。

仕事で Nuxt.js の app.html の仕様の説明ついでに、 mustache 記法について説明を行った際に書いた Vue 3 サンプルで初めて気づいたので、メモ程度に残しておきます。

ちなみに Nuxt.js の app.html のテンプレート機能は lodash/template で実現しているみたいです。

delimiters とその挙動について

以下のサンプルコードとその挙動をみると割と簡単にイメージがつくと思います。パっとイメージがつかない場合、 delimiters の部分をコメントアウトしたり、またコメントアウトを解除してみたりしてください。

See the Pen Vue 3 delimiter by Potato4d (@potato4d) on CodePen.

Vue.js のテンプレートにおいて、基本的にテンプレートに対して JavaScript 側の状態を伝えるためには、 {{ val }} の形式で記述します。

これについては普段は特別この仕様について強く意識する必要もないですし、大抵の場合にはうまく動作します。

ただ実はこれは単なるデフォルトの挙動であり、delimiters というオプションを変更することで、上記のサンプルのような、 [[ val ]] を対応させることができるようになっています。

これは Vue 2.x では new Vue に依存しており、アプリケーション内で統一的な設定が必要でしたが、 Vue 3 では設定がコンポーネントに移り、コンポーネント単位でこれらを設定できるようになりました。

これによって例外のために全体の記法を崩すことなく、例外のコンポーネントにおいて限定的に delimiters を変更できるようになっています。

いつ使うの?

基本的に現代的な開発だとあまり使うことがない機能であることは間違いありません。

ではこれが必要なシチュエーションはいつか。それは、完全な SPA になっておらず、サーバーサイドのテンプレートエンジンで出力した HTML の上から Vue.js の要素をマウントする必要があるアプリケーションの運用時。例えば Rails の返す View 側で必要なデータを流し込んだあとに、その上から new Vue をするようなケースです。

使っているエンジンが ERB (<%= %>) や Slim である場合はデリミタの問題は起きませんが、サーバーサイド側のテンプレートエンジンが mustache の系譜を継ぐものの場合は動作が干渉してしまいます。

そういった問題をスマートに解決するために、限定的にデリミタが必要になる場合があります。

もしこういった事情でデリミタを設定している環境がある場合、 Vue 3 に上げると全体としては素直な Vue テンプレートが記述でき、幾分か楽になるかもしれません。

余談

それはそれとして、サーバーサイドのテンプレートエンジンで流し込んだ HTML をそのままマウントすると容易に脆弱性が生まれるので、 data attribute の JSON だけ渡してロジックは全部 Vue.js で持つ。みたいな解決方法をしたほうが安全です。

また、 Slim の場合属性値が空の場合の挙動が怪しく、 v-else などの挙動で微妙に問題が起こりやすいこともあるため、どちらにせよマウントするための root だけを用意し、マウント後の挙動については JS 側で倒す方向に寄せるべきかと思います。