🛰️

【技術選定】Vue3プロジェクトでBudouXの導入を検討する

2022/07/05に公開

こんにちは。とあるフロントエンドエンジニアです。

今回はVue3プロジェクトでBudouXを導入する方法と、Vue3アプリケーションでBudouXを導入する前に考慮したことを記載します。

あくまで検討事項なので一部推測が入ります。

そもそもBudouXとは

BudouX is the successor to Budou, the machine learning powered line break organizer tool.
(Githubより)

要するに機械学習を使った自動改行ツールです。日本語の文章など、デバイスによって改行位置がイマイチになってしまう問題に関して、適切なワードごとに<wbr>タグを挿入してくれます。

詳しい動きはデモなどをみてみてください。

詳細なBudouXの説明はこの記事では省略します。

BudouXのデモ

https://google.github.io/budoux/

Github

https://github.com/google/budoux/tree/main/javascript/

Vue3プロジェクトに導入する

1. BudouXをインストール

npm i budoux

https://www.npmjs.com/package/budoux

2. src/main.js(ts)にインポート

main.js(ts)
import 'budoux/module/webcomponents/budoux-ja';

ここでは、BudouXを適用させるテキストをラップするための下のようなWebコンポーネントをアプリケーション全体で使用できるようにしています。

<budoux-ja></budoux-ja>

3. 使用したいテンプレートでテキストをラップする

今回は動的にテキストが挿入される前提で使用しています。

sampleView.vue
<template>
  <budoux-ja>{{ text }}</budoux-ja>
</template>

<script setup>
const text = "するとあの鳥捕りは、二十疋ばかり、袋に入れてあげようと思いました。";
</script>

開発者ツールで中身を見てみると、shadow DOMの中で上記のテキストが区切りのいいところで<wbr>タグが入っているのが確認できます。

開発者ツールで改行が入っていることを確認する画像

実際に動作を見てみると、window幅を変えていくと<wbr>で区切られているワードごとに改行されていくのが確認できます。

BudouX検証動画

Vueのディレクティブを使用してバインドしても使える

Vueのテンプレートディレクティブであるv-textやv-htmlを使ったテキストのバインドもできます。

動的なテキストのバインドであればこのようなユースケースもあると思います。

ただし、v-htmlを使用する場合は後述のXSSの問題があるため、使用する際は注意する必要があります。

<template>
  <budoux-ja v-text="text" />
  <budoux-ja v-html="text" />
</template>

<script setup>
const text = "するとあの鳥捕りは、二十疋ばかり、袋に入れてあげようと思いました。";
</script>

BudouXを導入する際の注意点

BudouXを導入する際は、プロジェクトの要件と以下のBudouXの注意点を照らし合わせ、導入するか否かの判断材料としました。

  • IEは非対応
  • テキストの改行位置とデザインの差異
  • 意図した通りに改行されない可能性
  • i18n対応
  • 改行コードの意図的な挿入
  • HTMLのサニタイズ

一つずつ解説していきます。

IEは非対応

BudouXはIE非対応のツールになります。ですが2022年6月16日でIEのサポートが終了したので、今後ここら辺は考える必要性が少なくなると思います。

テキストの改行位置とデザインの差異

デザイン通り実装してもBudouXを導入すると自動的に改行されてしまうので、LPなど割とテキストの改行位置もビジュアルに影響するようなページでは、デザイン通りの位置で改行をコントロールすることは難しい可能性があります。

使用箇所は要検討です。

意図した通りに改行されない可能性

「東京特許許可局」や「インフラストラクチャー」のように、漢字が多く羅列する言葉や長いカタカナが続く言葉は場合によってはbudouxに挿入した際に改行が挿入されない可能性があることも注意が必要です。

budouxの改行の原理としては、機械学習によって文章をいい感じに区切って<wbr>を入れるというものになります。

<budoux-ja>私は山田太郎です。</budoux-ja>
↓出力後
私は<wbr>山田太郎<wbr>です。

これが長い単語になると、適切に<wbr>が挿入されず、親要素のスタイルによってはコンテンツをはみ出してしまう可能性があることにも注意が必要です。

なのでbudouxを導入したからといって、神羅万象、全ての言葉がいい感じに改行されるとは限らないということもプロジェクトとして許容できるか検討しておきましょう。

i18n対応

アプリケーションがi18n対応、いわゆる多言語の切り替えが可能な仕様な場合、コンポーネントなどの実装にコストがかかる可能性があると考えました。

BudouXでは、以下のようなWebコンポーネントを使用して、日本語と中国語の文章に改行を組み込むことができます。

<budoux-ja>今日は天気です。</budoux-ja> // 日本語
<budoux-zh-hans>今天是晴天。</budoux-zh-hans> // 中国語

一見すると日本語と中国後の両方に対応できるので良いのではないかと思うのですが、i18n対応の場合、基本的にはテキストの中身だけが変わる仕様になると思います。

その場合今の設定言語が日本語か中国語かを判定して、表示させる要素を変えなくてはならない可能性が出てきます。

実装する際はこんな感じになるのではないかと思われる。(あくまでイメージ)

<budoux-ja v-if="isJapanese">{{ text }}</budoux-ja> // 日本語ならこっち
<budoux-zh-hans v-if="!isJapanese">{{ text }}</budoux-zh-hans> // 中国語ならこっち

アプリケーションがコンポーネントベースの設計の場合、BudouXを使用するあらゆるコンポーネントでこのような実装をする必要があるため、ここら辺のロジックを共通化するなど、少し工夫が必要になりそうと考察。

改行コードの意図的な挿入

要件によっては特定の箇所で絶対に改行したい!という意図でバックエンド側から改行コード込みのテキストが送られてくるシチュエーションも考えられます。(要するに強制的に改行させる)

v-htmlを使う場合

強制的に改行させるには、以下のように文字列に<br />を含んでv-htmlで渡せば強制的に改行することはできます。

<template>
  <budoux-ja v-html="text" />
</template>

<script setup>
const text = "するとあの鳥捕りは、<br />二十疋ばかり、袋に入れてあげようと思いました。";
</script>

ただ、おそらくデータで送られてくる改行コードはLFの「\n」の場合の方が多いと思います。

const text = "するとあの鳥捕りは、\n二十疋ばかり、袋に入れてあげようと思いました。";

この場合、v-htmlディレクティブで渡す前に、このLF文字「\n」を<br />に変換するモジュールを噛ませる必要があります。

またv-htmlによるHTMLテキストのバインドは後述するXSSの脆弱性もあるため、HTMLのサニタイズも同時に噛ませる必要があります。

まとめると、バックエンド側から送られてきたテキストをv-htmlでデータバインドする場合、LF文字の変換とサニタイズの2つの処理を合わせたモジュールが必要になる可能性があります。

v-htmlを使わないで対応する場合

XSSの問題もあるため、可能な限りv-htmlは避けたいところです。ということで、cssを使って強制改行するの方法も記載します。

sampleView.vue
<template>
  <span class="sample">
    <budoux-ja>{{ text }}</budoux-ja>
  </span>
</template>

<script setup>
const text = "するとあの鳥捕りは、\n二十疋ばかり、袋に入れてあげようと思いました。";
</script>

<style scoped>
.sample {
  white-space: pre-wrap;
}
</style>

white-space: pre-wrap; などのスタイルを当てると、改行コードを改行として処理するため、「\n」できちんと改行できるようになります。

バックエンド側からの改行込みの文章のデータバインドはこのwhite-spaceプロパティを使うのが現実的そうです。

HTMLのサニタイズ

公式にこのような注意点が記載されています。

BudouX supports HTML inputs and outputs HTML strings with markup applied to wrap phrases, but it's not meant to be used as an HTML sanitizer. BudouX doesn't sanitize any inputs. Malicious HTML inputs yield malicious HTML outputs. Please use it with an appropriate sanitizer library if you don't trust the input.

要するに、BudouXは入力されたHTMLテキストに対してサニタイズして吐き出してくれるわけではないので、悪意のあるHTMLの文字列でもそのまま出力されてしまいます。そういう場合はサニタイズするためのサードパーティのライブラリを使ってね〜という注意点です。

先述のv-htmlでバインドするようなケースでは、Vueのドキュメントにも記載がある通り、XSS脆弱性があります。

<template>
  <budoux-ja v-html="text" />
</template>

そのため、使用する際はサニタイズしてくれるライブラリの併用を検討する必要があります。

BudouXとsanitize-htmlを併用する

今回はHTMLサニタイザーのライブラリであるsanitize-htmlと併用してみます。
https://github.com/apostrophecms/sanitize-html

1. sanitize-htmlをインストール

npm install sanitize-html

Vue × TypeScriptの場合はsanitize-htmlの型定義もインストールします。

npm install -D @types/sanitize-html

2. src/main.js(ts)にインポート

main.js(ts)にsanitize-htmlをインポートします。

main.js(ts)
import sanitizeHtml from 'sanitize-html';

続いて、サニタイズの処理をグローバルで使用できるようにします。

Vue3でグローバルで使用するためには、globalPropertiesというものを使います。

main.js(ts)
const app = createApp(App);
app.config.globalProperties.$sanitize = sanitizeHtml;
app.mount('#app');

これで、$sanitize()がグローバルで使用できるようになりました。

TypeScriptの場合は型を定義する

TypeScriptを使用している場合、$sanitize()を使用すると、型が一致しないため、エディタで警告が出てしまいます。

src/typingsディレクトリを作成し、その中に型の定義ファイルを作成して型を教えてあげます。

typings/sanitize.d.ts
import sanitizeHtml from 'sanitize-html';

declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $sanitize: typeof sanitizeHtml;
  }
}

3. サニタイズするテキストに$sanitize()を適用する

sampleView.vue
 <template>
-  <budoux-ja v-html="text" />
+  <budoux-ja v-html="$sanitize(text)" />
 </template>

 <script setup>
 const text = "するとあの鳥捕りは、<a onmouseover=alert('スクリプトが実行されています')>二十疋</a>ばかり、袋に入れてあげようと思いました。";
 </script>

これでtextの内容をバインドする際に、サニタイズされてからBudouXが適用されることになります。

sampleView.vue
<template>
  <budoux-ja v-html="text" />
</template>

<script setup>
const text = "するとあの鳥捕りは、<a onmouseover=alert('スクリプトが実行されています')>二十疋</a>ばかり、袋に入れてあげようと思いました。";
</script>

もしサニタイズせず、上のコードで実装すると、ひょんなことから「二十疋」にマウスオーバーすると、内部のスクリプトが実行されてしまいます。

上の例では、アラートが表示されます。

今回はsanitize-htmlを使用しましたが、いずれにせよ、BudouXをv-htmlでバインドする場合にはサードパーティのサニタイザーを併用する必要があるため、その点も技術選定に考慮します。

おわりに

今回は Vue3環境における BudouX の導入方法と技術選定の際の検討事項を載せました。

同じようなアーキテクチャを検討している場合の参考になればと思います。

Discussion