joicomponents
Version:
Design patterns to build native web components
169 lines (155 loc) • 6.06 kB
JavaScript
/**
* Step 1: set up generic polyfill framework
*/
(function () {
'use strict';
var que = [];
var flaggs = [];
window.polyfill = {
ready: function (waitFn) { //returns true when the function is run
if (!waitFn)
return;
if (que)
return que.push(waitFn);
if (!(waitFn instanceof Function))
return;
waitFn();
return true;
},
runWhenReady: function (pf) { //empties the que and returns a promise resolved when all is run
if (flaggs.length > 0) {
if (!pf)
return;
var index = flaggs.indexOf(pf);
if (index > -1)
flaggs.splice(index, 1); //mutates flaggs
else
console.error("Check your polyfills.");
if (flaggs.length > 0)
return;
}
var q = que;
que = undefined;
return Promise.all(q.map(function (fn) {
return fn instanceof Function ? fn() : fn;
}));
},
await: function (pf) {
flaggs.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 false;
}
// broken doc fragment (older Edge)
if (!(t.content.cloneNode() instanceof DocumentFragment)) {
return false;
}
// 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;
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.runWhenReady to pauseCustomElementsPolyfill when flushing the que of functions.
var origFlushQue = window.polyfill.runWhenReady;
window.polyfill.runWhenReady = function (flag) {
window.polyfill.pauseCustomElementsPolyfill();
return Promise.resolve(
origFlushQue(flag)
).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 (flag) {
HTMLTemplateElement.bootstrap(window.document);
return origFlushQue(flag);
};
})();
}
//step 4: set up methods for loading polyfill files async or sync
function loadScriptAsync(url, onLoad) {
var newScript = document.createElement('script');
newScript.src = url;
onLoad && 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/";
!SD && loadPolyfill(wcLoc + "webcomponents-sd.js", "SD");
!CE && loadPolyfill(wcLoc + "webcomponents-ce.js", "CE");
!TE && loadPolyfill(wcLoc + "webcomponents-sd-ce-pf.js", "TE");
!ES6 && loadPolyfill(wcLoc + "webcomponents-sd-ce-pf.js", "ES6");
!WA && loadPolyfill(waLoc + "web-animations.min.js", "WA");
!PE && loadPolyfill(peLoc + "pep.js"/*, "PE"*/);
// or add no polyfill name if you don't want the flush to wait for this particular polyfill.
window.polyfill.runWhenReady();
})();