概要
document.writeが入った外部jsを、Reactで作られたクライアント側で遅延実行したかったけど、様々な都合からできないことを知り、色々調べた結果、解決方法を発見しました。
この記事が解決する可能性のある状況
- adなどで、外部サーバーにホスティングされたJavaScriptファイル(以下、外部js) を、サイト上に
<script>
を使ってロードする必要がある - 外部jsに
document.write
が使われていてるが、その仕様を変更することが難しい。 - React で 実装する必要がある。
免責事項
- 本投稿の内容を実践し、なんらかの不利益を負った場合も、当方は責任を負いません。
結論
postscribe を使う
インストールなど
↑のgithubリポジトリ参照
コード
// Test.tsx
import React from 'react';
export default const Test: React.FC = () => {
React.useEffect(() => {
const postscribe = require('postscribe');
postscribe(
'#test',
'<script src="https://external.com/script_that_contains_document-write.js"></script>'
);
}, []);
return (
<div id="test"></div>
);
};
疑問と調査結果
なぜ、postscribeが必要なのか?
ぶち当たった問題と、その調査結果です。
1.普通にscriptタグ埋め込めば良いのでは?
静的なHTMLの場合
静的なHTMLファイルを配信しているのであれば、document.write 入りのjs
を配信している側の想定通りの使い方です。
しかし、 jsの動作完了までブラウザがロックされるなど、体験上の問題があります。
Reactの場合
Reactではうまく動作しません。
サーバーサイドでレンダリングされた<script>
はブラウザがロードしたときに動作し、document.write
を実行しますが、直後にReactのクライアントサイドの描画が走り、これを上書きしてしまいます(このタイミングでDOMの差が存在する場合、Waringが出力されます)
また、クライアントサイドで<script>
タグがレンダリングされても、jsがダウンロードされることも、実行されることもありません。
2. Reactのレンダリング後にDOMを直接操作すれば良いのでは?
stackoverflow にこのやり方が紹介されています。
この方法であれば、外部jsをコンポーネントのローディングの度に実行することができます。
が
document.write
は 動きません
何故か?
メモ: document.write は deferred または asynchronous のスクリプト内では 無視され、エラーコンソールに "A call to document.write() from an asynchronously-loaded external script was ignored" などのメッセージが表示されます。
https://developer.mozilla.org/ja/docs/Web/API/Document/write
という仕様だからです。
じゃあ、 asyncやdefer付けなきゃいいじゃん? と思うかもしれませんが、付いていない <script>
タグがページのロード後に追加されてもブラウザは無視します。
よって、この方法でも document.write
を動作させることはできません。
3. そういうできないことをできるようにしてしまう postscribe やばくね?
他のタグライティングライブラリは存在しますが(他のタグライティングライブラリを参照してください)、PostScribe は DOM プロキシと呼ばれるものを使用している点が斬新です。これは、document.write/innerHTML を使用して、ブラウザがネイティブにコンテンツを書くのと同じようにコンテンツを書くことを保証する方法です。複雑な構文解析やハックをすることなく、ブラウザと同じように動作します。
https://github.com/krux/postscribe#approach
普通にブラウザにできることで実行していてるだけで、ハックとかしていないから、問題ない、ってことかな?(適当)
最後に
またひとつ、レガシーに対応できる力を得てしまった...