Skip to content

Instantly share code, notes, and snippets.

@dlwhitehurst
Created March 21, 2019 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dlwhitehurst/482671bb86967ffb6f2ac26b1bbe2dad to your computer and use it in GitHub Desktop.
Save dlwhitehurst/482671bb86967ffb6f2ac26b1bbe2dad to your computer and use it in GitHub Desktop.
/**
*
* VirtualYou.io
*
* Copyright 2019 CI Wise Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://virtualyou.io
* @file ScriptCache.js
* @author David L. Whitehurst. MrJackdaw
* @since 1.0.0
*
* Before modification credits go to:
* https://gist.github.com/MrJackdaw/d111b5bcdfc8b71afba8ba6c55b3168f MrJackdaw gist
*
* Usage:
* 1. create a `ScriptCache` instance:
* const scriptCache = new ScriptCache(["http://remote.cdn.com/myLibrary.min.js", "http://..."]);
* 2. pass any functions that depend on window globals (from your script) into `scriptCache.onLoad`
*/
export default class ScriptCache {
static SCRIPT_STATUS = {
COMPLETE: "complete",
ERROR: "error"
}
constructor(scripts) {
this.loaded = [];
this.failed = [];
this.pending = [];
this.load(scripts);
}
onLoad(success, reject) {
if (reject) reject(this.failed);
if (success) success(this.loaded);
// You can use this block to run any additional setup required after
// the scripts have loaded
}
/**
* This will loop through and load any scripts passed into the class constructor */
load(scripts = []) {
if (!scripts.length) return;
const scriptPromises = [];
for (let script of scripts) {
scriptPromises.push(this.loadScript(script))
}
return Promise.all(scriptPromises);
}
/**
* This loads a single script from its source. The 'loading' action is wrapped
* in a promise, which should fail if the script cannot be fetched */
loadScript(script) {
if (this.loaded.indexOf(script) > -1) return Promise.resolve(script);
this.pending.push(script);
return this.createScriptTag(script)
.then((script) => {
this.loaded.push(script);
this.pending.splice(this.pending.indexOf(script), 1);
return script;
})
.catch((e) => {
this.failed.push(script);
this.pending.splice(this.pending.indexOf(script), 1);
})
}
/**
* This creates a <script> tag and appends it to the document body */
createScriptTag = (scriptSrc, onComplete) => new Promise((resolve, reject) => {
let resolved = false,
errored = false,
body = document.body,
tag = document.createElement("script");
const handleLoad = (event) => { resolved = true; resolve(scriptSrc); };
const handleReject = (event) => { errored = true; reject(scriptSrc); };
const handleComplete = () => {
if (resolved) return handleLoad();
if (errored) return handleReject();
const status = ScriptCache.SCRIPT_STATUS;
const state = tag.readyState;
if (state === status.COMPLETE) handleLoad();
else if (state === status.ERROR) handleReject();
}
// text/jsx
tag.type = "text/jsx";
tag.async = false;
// Replace 'onComplete' callback reference in some script tag urls (e.g. Google Maps V3)
if (scriptSrc.match(/callback=CALLBACK_NAME/)) {
const onCompleteName = "onScriptSrcLoaded";
scriptSrc = scriptSrc.replace(/(callback=)[^&]+/, `$1${onCompleteName}`)
window[onCompleteName] = handleLoad;
} else tag.addEventListener("load", handleLoad);
tag.addEventListener("error", handleReject);
tag.onreadystatechange = handleComplete;
tag.src = scriptSrc;
body.appendChild(tag);
return tag;
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment