React の中に描画された Script タグで読み込まれるコードを起動する

__html 中の Script タグは (ほとんどの場合) 起動しない

このブログは S3 上にアップロードした HTML を動的に読み込んだものから本文部分を抽出し、React の dangerouslySetInnerHTML={{ __html }} を使って記事として表示しています。

この場合、本文中に Script タグが含まれていた場合でも JavaScript が起動しない局面があります。

リロード時は正常に Twitter ツイートがレンダリングされる一方で、ブログ内のリンクや戻るボタンなどで遷移した場合は以下のように生の BlockQuote が表示されるのみとなってしまいます。

というわけでもろもろ Rust で一から書いて ccs811 + bme280 で二酸化炭素とか温度湿度とかとってかわいい OLED で表示するまでやった。しかし OLED 用にピクセル一個ずつ配置してると Flash + Starling が思い出され結局ここに戻ってくるんかいと pic.twitter.com/sNVsfKqgjZ

— ふとし (@mmmpa) August 8, 2020

(再現のために Script タグと class を省いています)

新しい Script タグとして挿入しなおすと起動する

そこで Script タグとして解釈されていないタグをもとに新しい Script タグを作成して挿入すると JavaScript が起動します。

// 起動していないと思われる Script を抽出する
document.querySelectorAll("script").forEach((script) => {
  // 新しいタグを作成する。
  const newScript = document.createElement("script");

  // 読み込むコードの src を旧 Script タグからコピーする。
  newScript.setAttribute("src", script.getAttribute("src") || "");

  // JavaScript によっては読み込んだ位置に何かを描画するものもあるので、
  // 旧タグの位置に新しいタグを挿入する。
  script.parentNode?.insertBefore(newScript, script);

  // 旧タグは削除する。
  script.parentNode?.removeChild(script);
});

遷移時には表示できなかった Twitter ツイートが以下のように表示されるようになりました。

注意

当然ですが、第三者が自由に HTML を編集できる場所で適用すると自由に悪意のあるコードを起動できるようになるので注意しましょう。