DMMグループ Advent Calendar 2019 12日目の記事です。
アドカレ映えしない、地味~な内容ですが、よろしくお願いします。
TL;DR
Angular / React / Vue製サイトにWebAPI経由で返されたDOMを挿入する
Angular | React | Vue |
---|---|---|
前書き
大規模Webサイトの各ページへ横断的に要素を挿入したい要件があるとします。
- ブランドロゴ、ナビゲーション、トラッキングタグなど
- 大規模なので、ページ間で管轄が分かれていたり、別々のアーキテクチャで構築されていたりする
- 要素の管轄は一部署に集約したい
これらの要件を満たす手段として、 DOM Stringを返すようなAPIが提供される ことがあります。
他の手段としては「DOMを構築・挿入するスクリプトを配布する」という方法もありますが、それと比べて以下のようなメリットが期待できます。
- 呼び出しタイミングや組み込み位置をある程度制御できる
- サーバサイド・クライアントサイドのどちらからでも呼び出せる
「クライアントサイドからでも呼び出せる」とはいえ、必ずしもすんなりできるとは限らず、特に昨今のJSフレームワークで開発されたWebサービスに組み込むにはハマりどころが色々あります。
今回はAngular, React, Vue製でクライアントサイドレンダリングするようなWebサイトにそのようなAPIを組み込む際の方法や注意を纏めています。
[全般]DomStringをDOMとしてrenderされるようにinnerHtmlラッパを使う
いずれのフレームワークもXSS対策のため、テンプレートやJSX中で使用される変数はHTMLエスケープされるようになっています。
そのため、APIレスポンスのDOM stringをそのままテンプレートやJSXへ書き出そうとしても、HTMLがプレーンテキストとして表示されるだけでHTML要素としては読み込まれません。
そこで、変数中のHTMLをHTML要素として表示するための機能が提供されておりますので、そちらを利用します。
大前提としてAPI側のXSS対策はなされているものとします。
Angular | React | Vue |
---|---|---|
innerHTML | dangerouslySetInnerHTML | v-html |
[linkタグ]Angularの場合はlinkタグの扱いに注意
APIレスポンスの中に、スタイルシートを読み込むためのlinkタグが含まれる場合の話です。
React, Vueは上記の方法でlinkタグを出力することが出来るのですが、AngularではinnerHTMLでHTML要素を出力する際に、やはりXSS対策のためlinkタグやscriptタグが削除されるようになっています。
これを回避する方法も提供されており、DomSanitizerのbypassSecurityTrustHtmlメソッドを利用することでlinkタグを出力することができます。
[scriptタグ]innerHTMLではJSを実行出来ない
APIレスポンスにscriptタグが含まれる場合は厄介です。
innerHTML(やこれまで挙げたFWのinnerHTMLラッパ)を利用することで、DOMやスタイルを反映させることができますが、scriptタグ経由のJSを呼び出すことは出来ません。
これはW3CのHTML5 scriptタグの仕様として記述されています。
When inserted using the document.write() method, script elements execute (typically synchronously), but when inserted using innerHTML and outerHTML attributes, they do not execute at all.
これについては、フレームワーク側のサポートはなく、動的にJSを実行する方法などに記載されているように、
-
document.createElement('script');
でscript要素を作成、 - domStringをパースしてscript要素を組み立てていき、
- 最終的に
document.body.appendChild
などで完成したscript要素を挿入する
という、骨の折れる作業をする必要がありそうです。
[実践]ヘッダー・フッターでコンテンツを挟み込む
ここで少し具体的な話になります。
APIのレスポンスとして、複数のUI、例えばヘッダーとフッターが一遍に返されるような場合はどのように実装すればよいでしょうか。
この場合、ヘッダー・フッターを1つのコンポーネントとして扱うのが良さそうです。
各フレームワークとも、似たようなかたちで任意のコンテンツを挟み込むようなコンポーネントを実装出来ます。
Angular | React | Vue |
---|---|---|
ng-content | props.children | slot |
実装例 / Angular
<navigation>
<!-- サイトコンテンツ -->
</navigation>
<div [innerHTML]='header'></div>
<ng-content></ng-content>
<div [innerHTML]='footer'></div>
実装例 / React
<Navigation>
<!-- サイトコンテンツ -->
</Navigation>
<div dangerouslySetInnerHTML={{ __html: header }} />
{this.props.children}
<div dangerouslySetInnerHTML={{ __html: footer }} />
実装例 / Vue
<navigation>
<!-- サイトコンテンツ -->
</navigation>
<div v-html="header"></div>
<slot></slot>
<div v-html="footer"></div>
おわり
- 本記事はDOMを返すようなAPIをmanageする、というのがテーマであり、共通UIを提供する手段としてDOMを返すAPIの設計を推奨するものではありません。
- アプリケーションをSSRする場合は更なる課題がありそうですので、それはまた次の機会に。
- 明日は @uruha さんより、Virtual DOMの時代の楽しい話が聞けるんじゃないかと思うので、乞うご期待!