joicomponents
Version:
Design patterns to build native web components
196 lines (180 loc) • 7.17 kB
JavaScript
/**
* Step 1: set up generic polyfill framework
*/
(function () {
'use strict';
var que = [];
var flags = [];
window.polyfill = {
ready: function (fn) { //returns true when the function is run
if (!fn)
return;
if (que)
return que.push(fn);
if (fn instanceof Function)
fn();
},
runWhenReady: function (pf) { //empties the que and returns a promise resolved when all is run
if (flags.length > 0) {
if (!pf)
return;
var index = flags.indexOf(pf);
if (index > -1)
flags.splice(index, 1); //mutates flaggs
else
console.error("Check your polyfills.");
if (flags.length > 0)
return;
}
var q = que;
que = undefined;
let p = Promise.all(q.map(function (fn) {
return fn instanceof Function ? fn() : fn;
}));
p.then(function () {
document.dispatchEvent(new CustomEvent("polyfill-ready", {bubbles: true}))
});
return p;
},
await: function (pf) {
flags.push(pf);
}
}
})();
(function () {
'use strict';
//step 2: Feature detection
var SD =
'attachShadow' in Element.prototype &&
'getRootNode' in Element.prototype &&
!(window.ShadyDOM && window.ShadyDOM.force);
var CE = window.customElements && !window.customElements.forcePolyfill;
var TE = (function () {
// no real <template> because no `content` property (IE and older browsers)
var t = document.createElement('template');
if (!('content' in t)) {
return true;
}
// broken doc fragment (older Edge)
if (!(t.content.cloneNode() instanceof DocumentFragment)) {
return true;
}
// broken <template> cloning (Edge up to at least version 17)
var t2 = document.createElement('template');
t2.content.appendChild(document.createElement('div'));
t.content.appendChild(t2);
var clone = t.cloneNode(true);
return (clone.content.childNodes.length === 0 ||
clone.content.firstChild.content.childNodes.length === 0);
})();
var ES6 = window.Promise && Array.from && window.URL && window.Symbol;
var WA = !!window.Element.animate || false;
var PE = !!window.PointerEvent;
//step 3: Update polyfill framework to support CE and TE
if (!CE) { //customElements-polyfill extensions for polyfill object
(function expandFrameworkForCEPolyfill() {
//adding methods to stop and restart polyfill
window.polyfill.pauseCustomElementsPolyfill = function () {
if (window.customElements && customElements.polyfillWrapFlushCallback) {
customElements.polyfillWrapFlushCallback(function () {
});
if (document.readyState === "loading")
document.addEventListener("DOMContentLoaded", function () {
customElements.upgrade(document);
});
}
};
window.polyfill.restartCustomElementsPolyfill = function () {
if (!window.customElements || !customElements.polyfillWrapFlushCallback)
return;
customElements.polyfillWrapFlushCallback(function (fn) {
fn();
});
customElements.upgrade(document);
};
//SuperFun around polyfill.ready() to patch bug in customElements polyfill
var origReady = window.polyfill.ready;
window.polyfill.ready = function (fn) {
//bug in customElements polyfill?
// if fn() customElements.define does not trigger a call to customElements.upgrade. why?
origReady(fn) && customElements.upgrade && customElements.upgrade(document);
};
//SuperFun around polyfill.runWhenReady to pauseCustomElementsPolyfill when flushing the que of functions.
var origFlushQue = window.polyfill.runWhenReady;
window.polyfill.runWhenReady = function () {
window.polyfill.pauseCustomElementsPolyfill();
return origFlushQue().then(function () {
window.polyfill.restartCustomElementsPolyfill();
}).catch(function (err) {
console.error(err);
window.polyfill.restartCustomElementsPolyfill();
});
};
})();
}
if (!TE) {
//SuperFun around polyfill.runWhenReady to bootstrap HTMLTemplates added to the DOM prior to the polyfill being loaded.
(function expandFrameworkForTEPolyfill() {
var origFlushQue = window.polyfill.runWhenReady;
window.polyfill.runWhenReady = function () {
HTMLTemplateElement.bootstrap(window.document);
return origFlushQue();
};
})();
}
//step 4: set up methods for loading polyfill files async or sync
function loadScriptAsync(url, onLoad) {
var newScript = document.createElement('script');
newScript.src = url;
onAsyncLoad && newScript.addEventListener('load', onLoad);
document.head.appendChild(newScript);
}
function loadScriptSync(url, onSyncLoadAsString) {
var newScript = document.createElement('script');
newScript.src = url;
onSyncLoadAsString && newScript.setAttribute("onload", onSyncLoadAsString);
document.write(newScript.outerHTML);
}
/**
* @param url to the polyfill
* @param polyfillCode, the polyfill.runWhenReady() will not run until this code has been removed from the polyfill flaggs.
* if no code added, the polyfill.runWhenReady() will not await this polyfill.
*/
function loadPolyfill(url, polyfillCode) {
if (polyfillCode)
window.polyfill.await(polyfillCode);
document.readyState === "loading" ?
loadScriptSync(url, "window.polyfill.runWhenReady('" + polyfillCode + "');") :
loadScriptAsync(url, () => window.polyfill.runWhenReady(polyfillCode));
}
//step 5: load polyfill based on feature
var wcLoc = "https://rawgit.com/webcomponents/webcomponentsjs/master/bundles/";
var waLoc = "https://cdnjs.cloudflare.com/ajax/libs/web-animations/2.3.1/";
var peLoc = "https://code.jquery.com/pep/0.4.3/";
if (SD && CE && ES6 && TE) {
//no need to load WC polyfill
} else if (!SD && !CE && ES6 && TE) {
loadPolyfill(wcLoc + "webcomponents-sd-ce.js", "WC");
} else if (!SD && ES6 && TE) {
loadPolyfill(wcLoc + "webcomponents-sd.js", "WC");
} else if (!CE && ES6 && TE) {
loadPolyfill(wcLoc + "webcomponents-ce.js", "WC");
} else if (!ES6 || !TE) { // NOTE: any browser that does not have template or ES6 features must load the full suite of polyfills.
loadPolyfill(wcLoc + "webcomponents-sd-ce-pf.js", "WC");
}
if (!WA) {
loadPolyfill(waLoc + "web-animations.min.js", "WA");
}
if (!PE) {
loadPolyfill(peLoc + "pep.js");
// or add no polyfill name if you don't want the flush to wait for this particular polyfill.
// loadPolyfill("https://code.jquery.com/pep/0.4.3/pep.js");
}
window.polyfill.runWhenReady();
// Or wait until the entire page is loaded before you flush polyfill.ready() functions anyway
// (function waitForDOMLoaded(){
// document.readyState === "loading" ?
// window.addEventListener("DOMContentLoaded", window.polyfill.runWhenReady) :
// window.polyfill.runWhenReady();
// })();
})();