はじめに
CDNからスクリプトを読み込むようにしてて、CDNに障害が発生した場合
複数候補指定できたら解決するんじゃね?
というハナシ
ざっくり方法
スクリプトの候補を複数準備しておく
第1候補から繰り返す
1. 候補が存在するか確認する
2. 存在する場合、SCRIPTタグとして追加して終了
サンプル
構成
localhost:8082
│ index.html
├─test1
├─test2
│ hello.js
└─test3
hello.js
URL
http://localhost:8082/test1/hello.js
第1候補のスクリプト(存在しない)http://localhost:8082/test2/hello.js
第2候補のスクリプトhttp://localhost:8082/test3/hello.js
第3候補のスクリプト
ソース
const hello = () => {
alert("Hello, world!");
}
"Hello, world!"というダイアログを表示するhello関数のみ!
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>スクリプトを読み込むサンプル</title>
<script>
/**
* スクリプトを読み込む
* @param {string} src 読み込むソース
* @return 読み込み結果
*/
const loadScript = (src) => {
// ソースの存在チェック
const request = new XMLHttpRequest();
request.open("GET", src, false);
request.send(null);
// ソースが存在した場合、スクリプトタグを追加
if (request.status === 200) {
let head = document.getElementsByTagName("head")[0];
let script = document.createElement("script");
script.src = src;
head.appendChild(script);
return true;
}
return false;
};
// スクリプトのソース一覧
const srcArray = [
"http://localhost:8082/test1/hello.js",
"http://localhost:8082/test2/hello.js",
"http://localhost:8082/test3/hello.js"
];
// ソースの配列を先頭から、読み込めるまで読み込んでいく
for (let i = 0; i < srcArray.length; i++) {
if (loadScript(srcArray[i])) {
console.log("Loaded script: " + srcArray[i]);
break;
}
}
window.onload = function () {
hello();
};
</script>
</head>
<body>
<button onclick="hello();">hello</button>
</body>
</html>
表示時とhelloボタン押下時に、読み込んだスクリプトのhello関数を実行する
サンプルを実行
画面
表示時
"Hello, world!"というダイアログが表示されている
helloボタン押下時
"Hello, world!"というダイアログが表示されている
コンソール
1行目のワーニングは、XMLHttpRequestを使って同期でリクエストを送るのは非推奨なので表示
2行目のエラーは、第1候補のスクリプトが存在しないため表示
3行目のログは、第2候補のスクリプトが読み込まれたという表示
かんそう
思いつきで書いたので、サンプルをそのまま使うのは厳しい…
折角なのでメモの代わりに記事として残しておきます。
追記(2021/06/16)
コメントを貰って、存在チェックはerrorイベントで良いと感じて書き直してみました!
サンプル
本体
/**
* スクリプトローダー
* @class ScriptLoader
*/
class ScriptLoader {
/**
* コンストラクター
* @memberof ScriptLoader
*/
constructor() {
this.index = 0;
this.srcArray = [];
this.callback = null;
}
/**
* スクリプトのソースをセットする
* @param {string} src ソース
* @param {string} [backupSrc=null] ソースのロードに失敗した場合の、バックアップソース
* @memberof ScriptLoader
*/
setScript = (src, backupSrc = null) => {
this.srcArray.push({src:src, backupSrc:backupSrc});
}
/**
* セットしたスクリプトのソースを、スクリプトとしてロードする
* @param {function} [callback=null] 完了時のコールバック
* @memberof ScriptLoader
*/
loadScripts = (callback = null) => {
this.callback = callback;
this.index = 0;
this.loadScirpt(this.srcArray[this.index].src, this.srcArray[this.index].backupSrc);
};
/**
* スクリプトをロードする(内部)
* @param {*} src ソース
* @param {*} backupSrc ソースのロードに失敗した場合の、バックアップソース
* @memberof ScriptLoader
*/
loadScirpt = (src, backupSrc) => {
let script = document.createElement('script');
script.src = src;
script.addEventListener('load', {
scriptLoader: this,
handleEvent: function (event) {
this.scriptLoader.index++;
if (this.scriptLoader.index < this.scriptLoader.srcArray.length) {
// 次のソース配列が存在する場合
this.scriptLoader.loadScirpt(
this.scriptLoader.srcArray[this.scriptLoader.index].src,
this.scriptLoader.srcArray[this.scriptLoader.index].backupSrc);
} else {
// 次のソース配列が存在しない場合
this.scriptLoader.srcArray.length = 0;
if (this.scriptLoader.callback && typeof this.scriptLoader.callback === 'function') {
this.scriptLoader.callback();
}
}
}
}, false);
script.addEventListener('error', {
backupSrc: backupSrc,
delete: script,
scriptLoader: this,
handleEvent: function (event) {
if (this.backupSrc) {
// バックアップソースが設定されている場合
this.delete.remove();
this.scriptLoader.loadScirpt(this.backupSrc, null);
} else {
// バックアップソースが設定されてない場合
this.scriptLoader.index++;
if (this.scriptLoader.index < this.scriptLoader.srcArray.length) {
// 次のソース配列が存在する場合
this.scriptLoader.loadScirpt(
this.scriptLoader.srcArray[this.scriptLoader.index].src,
this.scriptLoader.srcArray[this.scriptLoader.index].backupSrc);
} else {
// 次のソース配列が存在しない場合
this.scriptLoader.srcArray.length = 0;
if (this.scriptLoader.callback && typeof this.scriptLoader.callback === 'function') {
this.scriptLoader.callback();
}
}
}
}
}, false);
let head = document.getElementsByTagName('head')[0];
head.appendChild(script);
};
}
サンプルを実行
<head>
<script src="scriptLoader.js"></script>
<script>
let loader = new ScriptLoader();
loader.setScript('xxxxxxxxxx/hello.js', 'src/hello.js');
loader.setScript('src/world.js');
loader.loadScripts(() => {
// スクリプト読み込み完了時に呼び出される
});
</script>
</head>
xxxxxxxxxx/hello.jsが読み込めた場合
<head>
<script src="scriptLoader.js"></script>
<script>
let loader = new ScriptLoader();
loader.setScript('http://xxxxxxxxxx/hello.js', 'src/hello.js');
loader.setScript('src/world.js');
loader.loadScripts(() => {
// スクリプト読み込み完了時に呼び出される
});
</script>
+ <script src="http://xxxxxxxxxx/hello.js"></script>
+ <script src="src/world.js"></script>
</head>
xxxxxxxxxx/hello.jsが読み込めなかった場合
<head>
<script src="scriptLoader.js"></script>
<script>
let loader = new ScriptLoader();
loader.setScript('http://xxxxxxxxxx/hello.js', 'src/hello.js');
loader.setScript('src/world.js');
loader.loadScripts(() => {
// スクリプト読み込み完了時に呼び出される
});
</script>
+ <script src="src/hello.js"></script>
+ <script src="src/world.js"></script>
</head>
かんそう2
割とちゃんと動くと思います。
ただ実用性があるかは謎のままです。