去年同じようなことをした際は、読み込む側でimport
・読み込まれる側でexport
を指定したうえで、htmlで<script src>
する際はtype="module"
を付与していた。
で、これをやるとDOM操作で任意のタグにonclick="..."
を指定しても関数が見つからないというスコープが変化することによるエラーが発生するんだけど、去年この辺をまとめたときに解決策も書いていたのに完全に忘却しており同じエラーに再度ハマるというやらかしが発生してしまい、その副産物として追加の別解が1つ見つかったのでそれについてメモ。
(案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に閉じるという状態になる。
注意点
ただしこれも確認した限り以下の制限がある。
body
へのappendChild()
を行うためDOMの読み込みが完了している必要がある- 外部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に定義されている処理を呼び出す })
サンプルコード
我ながらわかりづらい文章だと思う。あとで見直した方がよさそう。。
というか去年試した方法を含めても、どれも一長一短な感じ。「この方法がセオリー」みたいなのってないのかな。
(というか多分この辺はフレームワークが面倒みてそうな気もするんで、生html/javascript書いてる人の方が少ないのかも)