Skip to content

Instantly share code, notes, and snippets.

@MrJackdaw
Last active April 18, 2019 13:48
Show Gist options
  • Save MrJackdaw/d111b5bcdfc8b71afba8ba6c55b3168f to your computer and use it in GitHub Desktop.
Save MrJackdaw/d111b5bcdfc8b71afba8ba6c55b3168f to your computer and use it in GitHub Desktop.
Reusable class for loading external script files (e.g. from CDN)
// Inspiration from: https://www.fullstackreact.com/articles/Declaratively_loading_JS_libraries/index.html
// ================================================================================
// Summary: a handy class for dynamically loading and using async JS libs in a ReactJS app
// ================================================================================
//
// 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);
}
/**
* Use this block to run any additional setup
* when the scripts have loaded (or failed)
*/
onLoad(onSuccess, onReject) {
if (onReject) onReject(this.failed);
if (onSuccess) onSuccess(this.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();
}
tag.type = "text/javascript";
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;
})
}
@mrdark69
Copy link

Nice solution!!
But I have one issues when integrate with react-hot-loader.
Could you advise?

@MrJackdaw
Copy link
Author

Hey @mrdark69! I never saw your comment, so apologies for the extremely belated response.

I haven't used react-hot-loader before, so I'm not sure I would be able to help. (Please note I didn't create this script: just made a few modifications from the original, linked at the top.) I noticed however that the original gist had a bug, where nothing was happening in ScriptCache.onLoad because ...nothing was being called. I updated the code, though I'm not sure if it helps. If not, describe your issue here and I'd be happy to help take a look when I can.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment