script reload improvements

This commit is contained in:
sbwalker
2025-01-16 14:06:13 -05:00
parent 4630ee6e93
commit 0204ff8dd5
7 changed files with 129 additions and 67 deletions

View File

@ -1,67 +1,74 @@
const scriptInfoBySrc = new Map();
const scriptKeys = new Set();
export function onUpdate() {
// determine if this is an enhanced navigation
let enhancedNavigation = scriptKeys.size !== 0;
// iterate over all script elements in document
const scripts = document.getElementsByTagName('script');
for (const script of Array.from(scripts)) {
// only process scripts that include a data-reload attribute
if (script.hasAttribute('data-reload')) {
let key = getKey(script);
if (enhancedNavigation) {
// reload the script if data-reload is always or if the script has not been loaded previously and data-reload is once
let dataReload = script.getAttribute('data-reload');
if (dataReload === 'always' || (!scriptKeys.has(key) && dataReload == 'once')) {
reloadScript(script);
}
}
// save the script key
if (!scriptKeys.has(key)) {
scriptKeys.add(key);
}
}
}
}
function getKey(script) {
if (script.hasAttribute("src") && script.src !== "") {
if (script.src) {
return script.src;
} else if (script.id) {
return script.id;
} else {
return script.innerHTML;
}
}
export function onUpdate() {
let timestamp = Date.now();
let enhancedNavigation = scriptInfoBySrc.size !== 0;
// iterate over all script elements in page
const scripts = document.getElementsByTagName("script");
for (const script of Array.from(scripts)) {
let key = getKey(script);
let scriptInfo = scriptInfoBySrc.get(key);
if (!scriptInfo) {
// new script added
scriptInfo = { timestamp: timestamp };
scriptInfoBySrc.set(key, scriptInfo);
if (enhancedNavigation) {
reloadScript(script);
}
} else {
// existing script
scriptInfo.timestamp = timestamp;
if (script.hasAttribute("data-reload") && script.getAttribute("data-reload") === "true") {
reloadScript(script);
}
function reloadScript(script) {
try {
if (isValid(script)) {
replaceScript(script);
}
}
// remove scripts that are no longer referenced
for (const [key, scriptInfo] of scriptInfoBySrc) {
if (scriptInfo.timestamp !== timestamp) {
scriptInfoBySrc.delete(key);
} catch (error) {
if (script.src) {
console.error(`Script Reload failed to load external script: ${script.src}`, error);
} else {
console.error(`Script Reload failed to load inline script: ${script.innerHTML}`, error);
}
}
}
function reloadScript(script) {
try {
replaceScript(script);
} catch (error) {
if (script.hasAttribute("src") && script.src !== "") {
console.error("Failed to load external script: ${script.src}", error);
} else {
console.error("Failed to load inline script: ${script.innerHtml}", error);
}
function isValid(script) {
if (script.innerHTML.includes('document.write(')) {
console.log(`Script using document.write() not supported by Script Reload: ${script.innerHTML}`);
return false;
}
return true;
}
function replaceScript(script) {
return new Promise((resolve, reject) => {
var newScript = document.createElement("script");
var newScript = document.createElement('script');
// replicate attributes and content
for (let i = 0; i < script.attributes.length; i++) {
newScript.setAttribute(script.attributes[i].name, script.attributes[i].value);
}
newScript.innerHTML = script.innerHTML;
newScript.removeAttribute('data-reload');
// dynamically injected scripts cannot be async or deferred
newScript.async = false;
@ -70,11 +77,10 @@ function replaceScript(script) {
newScript.onload = () => resolve();
newScript.onerror = (error) => reject(error);
// remove existing script
// remove existing script element
script.remove();
// replace with new script to force reload in Blazor
// replace with new script element to force reload in Blazor
document.head.appendChild(newScript);
});
}
}