Help us understand the problem. What is going on with this article?

v-htmlで外部読み込みscriptタグを実行したい

More than 1 year has passed since last update.

モチベーション

はてなブログからWordpress REST API + Nuxt.js製の自家製ブログに移行をしている。

はてなブログで30個ぐらいの記事を書いてきた。

ある程度サイトができたので、記事を巡回して動きを確かめていたらTwitterウィジェットとGistの外部埋め込みが動いていないのがわかった。

どうやらv-htmlはinnerHTMLを使っているためscriptタグがあっても実行しないそうだ。

まあ、セキュリティ的にはそうだよね。

けど、今まで動いてきた資産は動かしたいし、Wordpressとして機能するものが動かないというのは納得がいかない。

なので、頑張って動かしてみることとした。

解決策

mounted, updatedでできあがったDOMをいじくり回すmethodsを呼び出す。

既存の<script>タグを動くように置換する。

jQuery使わずにDOMをいじくり回そう。

    methods: {
      runScript () {
        const scripts = this.$el.querySelectorAll('script')
        scripts.forEach(script => {
          const parentNode = script.parentNode
          let alternativeNode
          // todo: ホワイトリスト方式にする
          if (script.src.indexOf('https://gist.github.com/') !== -1) {
            alternativeNode = document.createElement('iframe')
            alternativeNode.src = URL.createObjectURL(new Blob(['<!DOCTYPE html><title></title>' + script.outerHTML], {type: 'text/html'}))
            alternativeNode.onload = () => {
              alternativeNode.height = alternativeNode.contentDocument.body.scrollHeight + 50
            }
          } else {
            alternativeNode = document.createElement('script')
            alternativeNode.src = script.src
          }
          parentNode.replaceChild(alternativeNode, script)
        })
      }
    },
    mounted () {
      this.runScript()
    },
    updated () {
      this.runScript()
    }

Twitterウィジェットなど多くの<script>タグの場合

<script>タグを再配置するだけでOK

Document.writeを使ってその場所に書き込む系の<script>タグの場合

<script>タグを再配置しただけだと、Document.writeは次のようなエラーがconsoleに出て実行できない。

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened

後読みコンテンツでDocument.writeできないということらしい。

そこでiframeを生成し、その中でスクリプトが実行するようにすれば良いと教えてもらった

iframeの高さは読み込み終わる前にはわからないため、読み込み終わった後に高さを差し替えている。

どれがDocument.writeのコンテンツなのか

これについては外見ではわからない。

Document.writeの外部読み込みスクリプトは非一般的とみなして、見つけ次第ホワイトリスト形式で振り分けてあげる形にしようと思う(まだ実装していない)

単体のコンポーネントにしている

見通しを良くするために、記事の部分という単一コンポーネントに切り出してしまっている。

<template>
  <div class="content" id="article-content" v-html="content"></div>
</template>

<script>
  export default {
    name: 'ArticleContent',
    props: {
      content: String
    },
    methods: {
      runScript () {
        const scripts = this.$el.querySelectorAll('script')
        scripts.forEach(script => {
          const parentNode = script.parentNode
          let alternativeNode
          // todo: ホワイトリスト方式にする
          if (script.src.indexOf('https://gist.github.com/') !== -1) {
            alternativeNode = document.createElement('iframe')
            alternativeNode.src = URL.createObjectURL(new Blob(['<!DOCTYPE html><title></title>' + script.outerHTML], {type: 'text/html'}))
            alternativeNode.onload = () => {
              alternativeNode.height = alternativeNode.contentDocument.body.scrollHeight + 50
            }
          } else {
            alternativeNode = document.createElement('script')
            alternativeNode.src = script.src
          }
          parentNode.replaceChild(alternativeNode, script)
        })
      }
    },
    mounted () {
      this.runScript()
    },
    updated () {
      this.runScript()
    }
  }
</script>

<style scoped>
.content >>> iframe {
  width: 100%;
  border: none;
  overflow: hidden;
}
</style>

https://github.com/sakapun/nuepress/blob/master/components/ArticleContent.vue

個人ブログならばいいけれども、プロダクションに使えるコードなのか

自分のサイトで、API も自分が管理しているものなので、今回はこれでいいとなった

WordPressの記事の対応であれば、外部読み込みscriptは、もとのWordPressでは普通に実行されているものなので特に心配する必要はないと判断した。

しかし、他のシチュエーションで使っても良いコードかというのは非常に注意した方が良い。

ただしWordPressのREST APIを使う場合に、今までの記事がいっぱいある場合や、使えるコンテンツを制御したくないというときには、このテクニックは必須になるんじゃないだろうかなという風に思う。

sakapun
water-cell
地球人口100億の時代への農業革命をWebテクノロジで支える
https://water-cell.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした