Last active
April 18, 2019 13:48
-
-
Save MrJackdaw/d111b5bcdfc8b71afba8ba6c55b3168f to your computer and use it in GitHub Desktop.
Reusable class for loading external script files (e.g. from CDN)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
}) | |
} |
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
Nice solution!!
But I have one issues when integrate with react-hot-loader.
Could you advise?