Skip to content

Instantly share code, notes, and snippets.

@digggggggggg
Last active January 7, 2022 21:39
Show Gist options
  • Save digggggggggg/a16c244b0b86d644ee0b60640441bfc0 to your computer and use it in GitHub Desktop.
Save digggggggggg/a16c244b0b86d644ee0b60640441bfc0 to your computer and use it in GitHub Desktop.
function AppLoader(){
let loaded = {};
let handlers = {};
const ext = (u,e) => !!u.match(new RegExp("\.("+e+")([\?\#].*?)?$","gi"));
const join = (a,b) => `${a.replace(/\/+$/,'')}/${b.replace(/^\/+/,'')}`;
const install = function(AppName,AppRender){
handlers[AppName] = AppRender;
document.addEventListener(`${AppName}:render`, function (event) {
const options = event && event.detail;
const { element = null } = options || {};
// must be CustomEvent, must use detail prop
// See https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events#creating_custom_events
// See https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
if (!element) {
console.error('Must define element in detail prop of CustomEvent', {name: AppName, event});
return;
}
AppRender(element, options);
});
};
const render = function(AppName, element = null, data = {}){
const event = `${AppName}:render`;
const detail = Object.assign({ element }, data );
document.dispatchEvent(new CustomEvent(event, { detail }));
// must be CustomEvent, must use detail prop
// See https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events#creating_custom_events
// See https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
};
const getScript = function(url,success){
var done=false,
head=document.getElementsByTagName('head')[0],
script=document.createElement('script')
;
script.src = url;
script.onload=script.onreadystatechange = function(){
if ( !done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') ) {
done=true;
if(typeof(success)=='function') success(script); //{url,script});
script.onload = script.onreadystatechange = null;
}
};
head.appendChild(script);
};
const getStyle = function(url,success){
var done=false,
head=document.getElementsByTagName('head')[0],
style=document.createElement('link')
;
style.href = url; style.rel = 'stylesheet';
style.onload = style.onreadystatechange = function(){
if ( !done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') ) {
done=true;
if(typeof(success)=='function') success(style); //{url,style});
style.onload = style.onreadystatechange = null;
}
};
head.appendChild(style);
};
const getAsset = function(url){
return new Promise((resolve,reject)=>{
const done = (obj) => {
resolve(obj);
}
if(!!loaded[url]){//loaded.filte(x=>x==url).length){
done(loaded[url])
}
else{
try{
const got = (obj) => {
loaded[url] = obj;
done(obj);
}
if(ext(url,"js")){
getScript(url,got);
}
else if(ext(url,"css")){
getStyle(url,got);
}
else{
console.warn("AppLoader: error loading resource", {url});
//throw new Error("can't load this type of file", {url})
got(url);
}
}
catch(e){
console.error("AppLoader: error loading resource", {url})
reject(e);
}
}
})
};
const getManifest = async function(manifest){
if(!!loaded[manifest]){
return loaded[manifest];
}
const assets = await fetch(manifest,{
credentials:"omit",
//mode:"no-cors"
})
.then(x=>x.json())
.then(x=>{
// detect other types of manifests
return x.entrypoints.filter(
x=>
!!x.match(/\b(static)\b/)
);
});
loaded[manifest] = assets;
return assets;
}
const load = async function(props = {}){
let { host, manifest, assets, name } = props;
if(!name){
return;
}
if(!assets){
assets = props.source || props.js || props.scripts;
}
if(!assets){
if(!manifest && !!host){
manifest = "asset-manifest.json";
}
if(!!manifest && !!host && !manifest.match(/^(\/\/|http)/gi)){
manifest = join(host, manifest);
}
if(!!manifest){
assets = await getManifest(manifest);
if(!!host){
assets = assets.map(x=>{
if(!x.match(/^(\/\/|http)/)){
x = join(host, x); //`${host.replace(/\/+$/,'')}/${x.replace(/^\/+/,'')}`;
}
return x;
})
}
}
}
else{
if(typeof(assets)=="string"){
// split string into array of assets to load
assets = assets.split(/[\s\t\r\n\;\,]+/gi)
}
}
let scripts = [], styles = [], images = [];
assets.forEach((url)=>{
if(ext(url,"js")){
scripts = scripts.concat(url);
}
else if(ext(url,"css")){
styles = styles.concat(url);
}
else{
images = images.concat(url);
}
});
// get all styles at once, don't care about how they load
styles.map(url=>getAsset(url));
// start with current being an "empty" already-fulfilled promise
var current = Promise.resolve();
scripts = await Promise.all(scripts.map((url)=>{
return current.then( ()=> getAsset(url) );
})).then((results)=>{
return results;
});
const result = { name, assets, styles, scripts, images };
return result;
};
const watch = function(){
const observerElement = document.body;
const observerOptions =
{
subtree: true,
childList: true,
}
;
let observer = new MutationObserver(mutations => {
for(let mutation of mutations) {
// examine new nodes, is there anything to highlight?
for(let node of mutation.addedNodes) {
// we track only elements, skip other nodes (e.g. text nodes)
if (!(node instanceof HTMLElement)) continue;
// check the inserted element for being a code snippet
if (node.matches('[app-loader]')) {
mount(node);
}
// or maybe there's a code snippet somewhere in its subtree?
for(let elem of node.querySelectorAll('[app-loader]')) {
mount(elem);
}
}
}
});
observer.observe(
observerElement,
observerOptions
);
return observer;
};
const mount = function(element){
return new Promise((resolve,reject)=>{
let props = {};
Array.prototype.slice.call(element.attributes)
.filter(p=>p.nodeName && p.nodeName!="app-loader" && p.nodeName.match(/^app-/))
.forEach(p=>{
props[p.nodeName.replace(/^app-/gi,'')] = p.nodeValue
})
;
const { name } = props;
load(props)
.then(()=>{
render(name, element, props);
resolve(element, props);
})
.catch((err)=>{
console.error("AppLoader: mount error", {err,element,name});
reject(err, {element,name});
})
})
}
const start = function(){
document.querySelectorAll('[app-loader]').forEach(element=>{
mount(element);
});
watch();
};
return {
loaded,
handlers,
install,
render,
getScript,
getStyle,
getAsset,
watch,
mount,
start,
}
};
(function(){
const appL = ()=> {
window.fwx = window.fwx || {};
window.fwx.app = AppLoader();
window.fwx.app.start();
}
var raf = requestAnimationFrame || mozRequestAnimationFrame || webkitRequestAnimationFrame || msRequestAnimationFrame;
if(raf) raf(appL);
else if(window.addEventListener) window.addEventListener('load', appL);
else if(window.attachEvent) window.attachEvent('onload', appL);
else window.onload = appL;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment