Google タグマネージャーでは type=”module” な script 要素は実行されない

Google タグマネージャー(以下、GTM)でちょっとハマったので、記事にしておこうかと思います。

type="module"script 要素は実行されない

GTM の「カスタム HTML」で

script type="module" src="/foo.js">script>

のように設定したとします。

この場合、/foo.js のスクリプトが実行されるかと思いきや、実際には実行されません。ちなみに type="module" をつけずに

script src="/foo.js">script>

とすれば、期待どおり普通にスクリプトが実行されます。

また、インラインのスクリプトも同様で、type="module" がついていると実行されません。

まとめると以下のようになります。type="module" なスクリプトは実行されません


script type="module" src="/foo.js">script>
script type="module">console.log('Hi!')script>


script src="/foo.js">script>
script>console.log('Hi!')script>

GTM 経由でサードパーティースクリプトを使うユーザーにとっても、逆にサードパーティースクリプトを開発・提供する側にとっても、これはちょっと気を付ける必要がありそうですね。

では、どうしてこうなるんでしょう?「GTM が type="module" をサポートしていないから」と言ってしまえばそれまでなんですが、内部的にはどうなっているのか、気になったのでちょっと見てみました。

実行されないけど、script 要素は DOM に追加されている

実は開発者ツールで見てみると、script 要素はちゃんと DOM に追加されているように見えます。例えば、「カスタム HTML」に以下の内容を設定すると、


script type="module" src="/foo.js">script>
script type="module">console.log('Hi!')script>

ページの DOM は以下のようになります。script 要素が DOM に追加されているのにもかかわらず、スクリプトは実行されていません。不思議ですね。

比較のために、type="module" をつけないバージョンでも見てみましょう。


script src="/foo.js">script>
script>console.log('Hi!')script>

DOM 上では余分な属性がいろいろついていますが、こちらは普通にスクリプトが実行されます。

状況をまとめると、こうなります。

  • type="module" の有無にかかわらず、DOM 上には script 要素は追加されている
  • ただし、type="module" なスクリプトは実行されない

うーん……なぜ type="module" だと実行されないのか、これだけだとよくわかりませんね 🤔

GTM の内部処理を追いかけてみる

よくわからないので、もうちょっと GTM の中まで見てみましょう。ちなみに、GTM の「カスタム HTML」は以下のような仕組みになっています。

  1. ユーザーが Web ページを開く
  2. HTML に貼り付けた GTM のスニペットによって、GTM が初期化される
  3. タグのトリガーが発動すると、GTM によって「カスタム HTML」の内容が HTML に追加される
  4. (それによって、カスタム HTML に含まれている script 要素の内容が実行される)

デバッガで動きを見てみたところ、script 要素を DOM に追加する処理を見つけました。

ざっくり説明すると、「カスタム HTML」の内容は配列 b に入っていて、これを順次 e(= g)に取り出して処理していきます。gscript 要素だった場合、赤く囲った a.insertBefore(g, null) によって abody 要素)に追加されます。

ただよく見ると、「gscript 要素だった場合」の if 文に、g.type === "text/gtmscript" という条件もついています。これについてはちょっと説明が必要ですね。GTM の「カスタム HTML」では、type 属性のない script 要素は、GTM 側で type="text/gtmscript" という属性がつけられた上で、上記引用部分の処理に流れてきます。そのため、g.type === "text/gtmscript"true になります。

一方、type="module" である script 要素の場合は、type="text/gtmscript" という属性はつきません。そのため、この条件 g.type === "text/gtmscript"false となり、別の枝の方にある a.insertBefore(g, null) によって abody 要素)に追加されます。

まとめるとこうですが、まだ違いはよく分かりませんね。

  • A. '; const script = div.firstChild; document.body.insertBefore(script, null);

script 要素を直接作ったのであれば、スクリプトは実行される

const script = document.createElement('script');
script.innerHTML = 'console.log("Hi!")';
document.body.insertBefore(script, null); 

どちらも DOM 上には script 要素が追加されますが、それが実行されるか否かは異なります。

またもちろん、document.createElement('script')script 要素を作り直せば、スクリプトは実行されます。

const div = document.createElement('div');
div.innerHTML = '';
const script = div.firstChild;

const script2 = document.createElement('script');
script2.text = script.text
document.body.insertBefore(script2, null); 

ということで、長々と見てきて結局のところそこまで大した話ではないですが、type="module" だと GTM ではうまく動かない理由が分かりました。

  1. div.innerHTML への代入の結果、script 要素が作成される
  2. type="module" をつけていなければ、script 要素があらためて作り直される
  3. script 要素が DOM に追加される
  4. (2. で作り直されていれば、スクリプトが実行される)

余談ですが、GTM のこのあたりの処理では Trusted Types API も使われています。オッ! と思ってコードを追いかけてみましたが、今回の問題の本質とは別に関係はなかったようです。

おわりに

この記事では、type="module"script 要素が GTM で実行されない件について見てみました。GTM を使う側も、スクリプトを提供する側も、注意する必要がありそうですね。

ちなみに、GTM とスクリプトの関係を調べている途中で、以下のブログ記事を見つけました。こちらもおもしろいのでおすすめです。

https://techblog.raccoon.ne.jp/archives/1660010900.html


元の記事を確認する

関連記事