Skip to content

Instantly share code, notes, and snippets.

@jokeyrhyme
Last active June 29, 2019 08:38
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jokeyrhyme/2c57fb01e734e999eded24f1e52e1a6e to your computer and use it in GitHub Desktop.
experimentation with a script loader that de-duplicates requests for scripts
/**
load this prior to to all other scripts on the page,
then it will be able to monitor script loading properly
requires: Promise, MutationObserver, querySelectorAll, Map, Set, const, let
*/
;(() => {
'use strict'
function noop () {}
// note: never expose Promise of this type, not safe
function newOpenPromise () {
let temp
const promise = new Promise((resolve, reject) => {
temp = { resolve, reject }
})
Object.assign(promise, temp)
return promise
}
function once (fn) {
let isCalled = false
return function () {
if (!isCalled) {
try {
fn()
} catch (err) {}
}
isCalled = true
}
}
function isExternalScriptNode (node) {
return !!(node && node.tagName === 'SCRIPT' && node.src.trim())
}
function isExternalScriptNodeList (nodeList) {
return !!(nodeList && Array.prototype.some.call(nodeList, isExternalScriptNode))
}
function isExternalScriptMutation (mutation) {
return !!(mutation && mutation.addedNodes && isExternalScriptNodeList(mutation.addedNodes))
}
function getAddedNodes (mutation) {
return mutation.addedNodes
}
/*
interface ScriptRequest {
loaded: Array // args from onload
errored: Array // args from onerror,
promise: Promise
}
*/
const scriptRequests = new Map() // { String: ScriptRequest, ... }
function newRequest () {
return {
promise: newOpenPromise()
}
}
function getMatchingRequest (url) {
const request = scriptRequests.get(url) || newRequest()
scriptRequests.set(url, request)
return request
}
const SCRIPT_STATUS_ABSENT = 'SCRIPT_STATUS_ABSENT'
const SCRIPT_STATUS_REQUESTED = 'SCRIPT_STATUS_REQUESTED'
const SCRIPT_STATUS_LOADED = 'SCRIPT_STATUS_LOADED'
const SCRIPT_STATUS_EXECUTED = 'SCRIPT_STATUS_EXECUTED'
const SCRIPT_STATUS_DELETED = 'SCRIPT_STATUS_DELETED'
const SCRIPT_STATUS_ERROR = 'SCRIPT_STATUS_ERROR'
function getMatchingScriptElements (doc, url) {
return Array.prototype.filter.call(
doc.querySelectorAll('script[src]'),
(script) => script.src.trim() === url.trim()
)
}
function getScriptStatus (doc, url) {
const scripts = getMatchingScriptElements(doc, url)
if (!scripts.length) {
return SCRIPT_STATUS_ABSENT // or SCRIPT_STATUS_DELETED (??)
}
const request = scriptRequests.get(url.trim())
if (request && !(request.loaded || request.errored)) {
return SCRIPT_STATUS_REQUESTED
}
if (request.loaded) {
return SCRIPT_STATUS_LOADED
}
if (request.errored) {
return SCRIPT_STATUS_ERROR
}
}
const scriptObserver = new MutationObserver((mutations) => {
mutations
.filter(isExternalScriptMutation)
.map(getAddedNodes)
.forEach((addedNodes) => {
Array.prototype.filter.call(addedNodes, isExternalScriptNode)
.forEach((script) => {
const request = getMatchingRequest(script.src.trim())
script.addEventListener('load', (...args) => {
request.loaded = args
request.promise.resolve(args)
}, false)
script.addEventListener('error', (...args) => {
request.errored = args
request.promise.reject(args)
}, false)
})
})
})
scriptObserver.observe(document.documentElement, {
childList: true,
subtree: true
})
function injectScript (parent, url, cb = noop) {
const script = parent.ownerDocument.createElement('script')
script.async = true
script.defer = true
script.src = url.trim()
if (cb !== noop) {
const request = getMatchingRequest(url)
request.promise
.then((args) => {
cb(...args)
})
.catch((args) => {
cb(...args)
})
}
parent.appendChild(script)
}
window.injectScript = injectScript
function loadScriptOnce (parent, url, cb = noop) {
const doc = parent.ownerDocument
const status = getScriptStatus(doc, url)
if (status === SCRIPT_STATUS_ABSENT) {
injectScript(parent, url, cb)
return
}
if (cb !== noop) {
const request = getMatchingRequest(url)
request.promise
.then((args) => {
cb(...args)
})
.catch((args) => {
cb(...args)
})
}
}
window.loadScriptOnce = loadScriptOnce
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment