zaki work log

作業ログやら生活ログやらなんやら

[ブラウザJavaScript] htmlで読み込んだjsから更に外部jsをscriptエレメントで動的に作成して読み込む

去年同じようなことをした際は、読み込む側でimport・読み込まれる側でexportを指定したうえで、htmlで<script src>する際はtype="module"を付与していた。
で、これをやるとDOM操作で任意のタグにonclick="..."を指定しても関数が見つからないというスコープが変化することによるエラーが発生するんだけど、去年この辺をまとめたときに解決策も書いていたのに完全に忘却しており同じエラーに再度ハマるというやらかしが発生してしまい、その副産物として追加の別解が1つ見つかったのでそれについてメモ。

zaki-hmkc.hatenablog.com

(案3) JavaScriptからscriptエレメントを作成してjsファイルをセット

コードは以下の通り。
元々htmlから読み込まれたjsファイル内で、以下のように追加の外部jsファイルをセットするというもの。

const elm = document.createElement('script');
elm.src = 'library.js';
document.body.appendChild(elm);

<script>のエレメントを新規に作成・src属性に外部jsファイルを指定し、bodyへappendする。
これで「呼び出し元htmlから必要なjsファイルをすべて読み込む」のと同等の構成になる。
ただしhtmlの記述上は追加の外部jsファイルの存在を知る必要がないので、依存や必要なファイル名などはhtmlファイル上は気にしなくて済むようになり、jsに閉じるという状態になる。

注意点

ただしこれも確認した限り以下の制限がある。

  1. bodyへのappendChild()を行うためDOMの読み込みが完了している必要がある
  2. 外部jsを使った処理のタイミングが早すぎると未定義エラーになる

DOMの読み込み処理を待つ

正しい用語がわからないんだけど、jsファイルソースの地べたの部分に書かれた場合(htmlから読み込まれたら即処理される部分)、呼び出し元htmlから元のjsを読み込む<script>タグが<head>のように早い箇所に記述されているとdocument.bodyプロパティがまだ存在せずnullになりエラーとなる。

</body>の前後のようにhtmlの最後の方に書くか、DOMContentLoadedイベント発火時に書くと良い。

document.addEventListener("DOMContentLoaded", () => {
    const elm = document.createElement('script');
    elm.src = 'library.js';
    document.body.appendChild(elm);

}

外部jsを使った処理タイミング

これは仕様が規定されてるか不明で実測値の範囲だが、以下のタイミングではNGでどちらも外部jsの定義を参照できなかった。

  • DOMContentLoadedイベント発火内で外部jsを読み込んだ直後
  • </body>直前でhtmlから外部jsを読み込みつつ、DOMContentLoadedイベント内

試した限りでは、DOMContentLoadedよりも後の外部ファイルをすべて読み込んだタイミングのloadイベント内であれば動作した。

window.addEventListener('load', () => {
    // ここでlibrary.jsに定義されている処理を呼び出す
})

サンプルコード

github.com


我ながらわかりづらい文章だと思う。あとで見直した方がよさそう。。

というか去年試した方法を含めても、どれも一長一短な感じ。「この方法がセオリー」みたいなのってないのかな。
(というか多分この辺はフレームワークが面倒みてそうな気もするんで、生html/javascript書いてる人の方が少ないのかも)

zaki-hmkc.hatenablog.com