javascriptで外部scriptの読み込み方法の検討

JSにおける、外部モジュールの読み込みはprototypejsを参考にいくつかのバージョンを構築してきたが、各方法におけるメリットとデメリットをまとめておきたかったので、メモしておくことにする。
サーバーサイドモジュールではなく、あくまで通常のWEBページに書かれたSCRIPTタグで呼び出されるjavascriptで外部モジュールの読み込みを行う事を前提とする。
document.write
実装方法
記述は非常に簡単で下記の用に記述するだけ。
1 |
<script type="text/javascript" src="http://hoge.com/hoge.js"></script> |
src部分をプログラムで生成して複数の読み込みを行うことも可能だ。
さらに、現在表示しているURLと同じ場所に外部モジュールが存在する場合は、相対パスで記述もできる。
デメリット
非常に簡単に実装する方法はこの「document.write」であるワケだが、この関数には致命的な欠点が存在する。
僕が以前かいた会社のブログで詳細を見てもらいたいのだが、
document.writeがイベントタイミングによって挙動が違う
ようするに、読み込まれたJSでそのまま記述する分には問題ないのだが、settimeoutや、別イベントを待って処理するような事をした場合に、画面真っ白問題が発生するのである。
あと、さらに向かい風なのが、GoogleやYahooなどで最近利用が増えているタグマネージャー機能との相性が悪いという点である。
明確に「document.write禁止」とうたっているタグマネもあるぐらい、煙たがられている存在だ。
きっとタグマネ内でイベント後にJSを実行したいんだろう。
createElement -> appendChild
document.writeと考え方は変わらないのだが、画面が白くならない方法としてappendChild方式がとられることが多い。
実装方法
1 2 3 4 5 |
var s = document.createElement("script"); s.type = "text/javascript"; s.src = "http://hoge.com/hoge.js"; s.onload = function(){alert("読み込み確認")}; document.body.appendChild(s); |
メリット
上記の記述でモジュールが読み込み完了したタイミングもイベントとして受け取れるようになる。
document.writeを使うよりは有効であると考えていいだろう。
ちなみに、document.bodyをdocument.getElementsByClassName(“head”)[0]とするとheadタグ内で実行されるので、より高速にプログラムを起動できるようになる。
デメリット
bodyタグやheadタグが読み込まれていることが条件になり、読み込み前にこの処理を実行してしまうと、undefinedエレメントにappendする致命的なエラーになるので、この処理は必ず、bodyやheadのonload処理(またはDOMContentLoaded)を利用して行わなければいけない。
書けばできるので、この方法一式をスニペット化しておくと仕事で使う場合は効率があがるぞ!!
ajax -> eval
非同期を使って読み込みを行いたい場合はajax処理でやるのが一番。
実装方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
(function(w,d,n){ var $$={} /** * Ajax **/ $$.ajax = { createHttpRequest:function(){ //Win ie用 if(w.ActiveXObject){ //MSXML2以降用; try{return new ActiveXObject("Msxml2.XMLHTTP")} catch(e){ //旧MSXML用; try{return new ActiveXObject("Microsoft.XMLHTTP")} catch(e2){return null} } } //Win ie以外のXMLHttpRequestオブジェクト実装ブラウザ用; else if(w.XMLHttpRequest){return new XMLHttpRequest()} else{return null} }, //XMLHttpRequestオブジェクト生成 set:function(options){ if(!options){return} var httpoj = new $$.ajax.createHttpRequest(); if(!httpoj){return;} //open メソッド; option = $$.ajax.setOption(options); httpoj.open( option.method , option.url , option.async ); //type httpoj.setRequestHeader('Content-Type', option.type); //onload-check // httpoj.onreadystatechange = this.readystate(httpoj,option); httpoj.onreadystatechange = function(){ //readyState値は4で受信完了; if (httpoj.readyState==4){ //コールバック option.onSuccess(httpoj.responseText); } }; //query整形 var data = $$.ajax.setQuery(option); //send メソッド if(data.length){ httpoj.send(data.join("&")); } else{ httpoj.send(); } }, dataOption:{ url:"", query:{}, // same-key Nothing querys:[], // same-key OK data:{}, // ETC-data event受渡用 async:"true", // [trye:非同期 false:同期] method:"POST", // [POST / GET] type:"application/x-www-form-urlencoded", // [text/javascript]... //call-back onSuccess:function(res){console.log("Success:"+res)}, onError:function(res){console.log("Error:"+res)} }, setOption:function(options){ var option = {}; for(var i in $$.ajax.dataOption){ if(typeof options[i] != "undefined"){ option[i] = options[i]; } else{ option[i] = $$.ajax.dataOption[i]; } } return option; }, setQuery:function(option){ var data = []; if(typeof option.query != "undefined"){ for(var i in option.query){ data.push(i+"="+encodeURIComponent(option.query[i])); } } if(typeof option.querys != "undefined"){ for(var i=0;i<option.querys.length;i++){ if(typeof option.querys[i] == "Array"){ data.push(option.querys[i][0]+"="+encodeURIComponent(option.querys[i][1])); } else{ var sp = option.querys[i].split("="); data.push(sp[0]+"="+encodeURIComponent(sp[1])); } } } return data; }, _:0 }; __LIB = $$; })(window,document,navigator); __LIB.ajax.set({ "url" : "http://hoge.com/hoge.js", "method":"get", "async" :"true", "type" :"text/plain", "onSuccess":function(res){ eval(res); } }); |
メリット
上記の2タイプの方法は、HTMLソースにJS、タグを書き込んだのと同じ効果になるので、JSのプログラムソースが非常に分かりやすい状態になります。
ajaxを利用すると、ソースコードがメモリ内で展開されるので、ソースコードを隠す効果に使うことは可能です。
ただし、モジュール読み込みのURIを叩くと文字列が表示されるというお寒いサーバー構造にせずに、JSからの読み込みセッションのみ対応する工夫が必要にはなります。
デメリット
サーバー側で「x-frame-options」に対応しなければいけません。
これは外部ドメインからの応答に対応するかどうかを受け側のサーバー設定で行わないといけないので、禁止しているサーバー環境では使えない事があります。
そして、x-frame-optionsは最近のOSはデフォルト設定ではOFFになっているので、クロスドメインでajaxを使う事はもしかすると危険が多いかもしれません。
呼び出されるモジュールについて
呼び出されるモジュールは、大きく分けて下記の「静的」と「動的」になりますが、動的JSを利用する事で、ファイル単位で保存されているJSデータやJSONデータを、JSファイル基準で自由に呼び出すことが可能になるため、安定したWEBサービスを構築することも可能です。
静的ファイル
・通常のJSファイル
動的JS
・サーバーサイドのCGIでJSプログラムを出力する。
・サーバーに置かれたJSファイルのライブラリ郡から必要なモジュールのみをピックアップする。(CGI対応)
何故モジュール呼び出しが必要なのか?
そもそも、全てのソースコードやデータを1つのJSファイルにまとめてしまえばいいのでは?と考えるめんどくさがり屋さんもいるかもしれませんが、
不要なモジュールを読み込まないことにより、サーバートラフィックを落とすことも可能だと考えましょう。
トラフィックデータは、サービス提供会社も、利用ユーザーも、ふくらんだ結果、損をするだけなので、少ないに越したことはありません。
この辺のエコシステム構築も最近のWEBアプリ構築のトレンドかもしれませんね。
ちなみに、PHPを使うとデータ圧縮機能が使えてオトクです。
※もちろん、他のCGIでもOSレベルでも可能な場合がありますよ。
どれを使うのが良いのか?
結論で言えば、appendChildが安定感があり、ソースコード表示を許すのであれば、これが一番いいでしょう。
個人的にはajaxが好きなので同一サーバーで行う場合に限りOKじゃないでしょうか?
え?何?jQueryを使えば、こんなに面倒くさい事をしなくてもいい?
そういう人はこの記事は不要でしたね。
Pingback: 特定のページのときのみ外部JavaScriptファイルをJavaScriptファイルから読み込む – Acenumber Technical Issues
1年半前に書いたこの記事と似てるリンクを教えていただいてありがとうございます。
複製コピーでは無さそうなので、特に問題は無いですね。