172 lines
5.6 KiB
JavaScript
172 lines
5.6 KiB
JavaScript
const pageScriptInfoBySrc = new Map();
|
|
|
|
function getKey(element) {
|
|
if (element.src !== "") {
|
|
return element.src;
|
|
} else {
|
|
return element.content;
|
|
}
|
|
}
|
|
|
|
function registerPageScriptElement(element) {
|
|
let key = getKey(element);
|
|
let pageScriptInfo = pageScriptInfoBySrc.get(key);
|
|
|
|
if (pageScriptInfo) {
|
|
pageScriptInfo.referenceCount++;
|
|
} else {
|
|
if (element.src.startsWith("./")) {
|
|
element.src = new URL(element.src.substr(2), document.baseURI).toString();
|
|
}
|
|
pageScriptInfo = { src: element.src, type: element.type, integrity: element.integrity, crossorigin: element.crossorigin, content: element.content, location: element.location, reload: element.reload, module: null, referenceCount: 1 };
|
|
pageScriptInfoBySrc.set(key, pageScriptInfo);
|
|
initializePageScript(pageScriptInfo);
|
|
}
|
|
}
|
|
|
|
function unregisterPageScriptElement(element) {
|
|
const pageScriptInfo = pageScriptInfoBySrc.get(getKey(element));
|
|
if (!pageScriptInfo) {
|
|
return;
|
|
}
|
|
pageScriptInfo.referenceCount--;
|
|
}
|
|
|
|
async function initializePageScript(pageScriptInfo) {
|
|
if (pageScriptInfo.reload) {
|
|
const module = await import(pageScriptInfo.src);
|
|
pageScriptInfo.module = module;
|
|
module.onLoad?.();
|
|
module.onUpdate?.();
|
|
} else {
|
|
try {
|
|
injectScript(pageScriptInfo);
|
|
} catch (error) {
|
|
console.error("Failed to load script: ${pageScriptInfo.src}", error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onEnhancedLoad() {
|
|
for (const [key, pageScriptInfo] of pageScriptInfoBySrc) {
|
|
if (pageScriptInfo.referenceCount <= 0) {
|
|
if (pageScriptInfo.module) {
|
|
pageScriptInfo.module.onDispose?.();
|
|
}
|
|
pageScriptInfoBySrc.delete(key);
|
|
}
|
|
}
|
|
|
|
for (const [key, pageScriptInfo] of pageScriptInfoBySrc) {
|
|
if (pageScriptInfo.module) {
|
|
pageScriptInfo.module.onUpdate?.();
|
|
} else {
|
|
try {
|
|
injectScript(pageScriptInfo);
|
|
} catch (error) {
|
|
if (pageScriptInfo.src !== "") {
|
|
console.error("Failed to load script library: ${pageScriptInfo.src}", error);
|
|
} else {
|
|
console.error("Failed to load inline script: ${pageScriptInfo.content}", error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function injectScript(pageScriptInfo) {
|
|
return new Promise((resolve, reject) => {
|
|
var pageScript;
|
|
var script = document.createElement("script");
|
|
script.async = false;
|
|
|
|
if (pageScriptInfo.src !== "") {
|
|
pageScript = document.querySelector("page-script[src=\"" + pageScriptInfo.src + "\"]");
|
|
script.src = pageScriptInfo.src;
|
|
if (pageScriptInfo.type !== "") {
|
|
script.type = pageScriptInfo.type;
|
|
}
|
|
if (pageScriptInfo.integrity !== "") {
|
|
script.integrity = pageScriptInfo.integrity;
|
|
}
|
|
if (pageScriptInfo.crossorigin !== "") {
|
|
script.crossOrigin = pageScriptInfo.crossorigin;
|
|
}
|
|
} else {
|
|
pageScript = document.querySelector("page-script[content=\"" + CSS.escape(pageScriptInfo.content) + "\"]");
|
|
script.innerHTML = pageScriptInfo.content;
|
|
}
|
|
|
|
script.onload = () => resolve();
|
|
script.onerror = (error) => reject(error);
|
|
|
|
// add script to page
|
|
if (pageScriptInfo.location === "head") {
|
|
document.head.appendChild(script);
|
|
} else {
|
|
document.body.appendChild(script);
|
|
// note this throws an exception when page-script is on a page which has interactive components (ie Counter.razor)
|
|
// Error: Uncaught (in promise) TypeError: can't access property "attributes" of null
|
|
// this seems to be related to Blazor-Server-Component-State which is also injected at the end of the body
|
|
}
|
|
|
|
// remove page-script element from page
|
|
if (pageScript !== null) {
|
|
pageScript.remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
export function afterWebStarted(blazor) {
|
|
customElements.define('page-script', class extends HTMLElement {
|
|
static observedAttributes = ['src', 'type', 'integrity', 'crossorigin', 'content', 'location', 'reload'];
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.src = "";
|
|
this.type = "";
|
|
this.integrity = "";
|
|
this.crossorigin = "";
|
|
this.content = "";
|
|
this.location = "head";
|
|
this.reload = false;
|
|
}
|
|
|
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
switch (name) {
|
|
case "src":
|
|
this.src = newValue;
|
|
break;
|
|
case "type":
|
|
this.type = newValue;
|
|
break;
|
|
case "integrity":
|
|
this.integrity = newValue;
|
|
break;
|
|
case "crossorigin":
|
|
this.crossorigin = newValue;
|
|
break;
|
|
case "content":
|
|
this.content = newValue;
|
|
break;
|
|
case "location":
|
|
this.location = newValue;
|
|
break;
|
|
case "reload":
|
|
this.reload = newValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
connectedCallback() {
|
|
registerPageScriptElement(this);
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
unregisterPageScriptElement(this);
|
|
}
|
|
});
|
|
|
|
blazor.addEventListener('enhancedload', onEnhancedLoad);
|
|
} |