add ability to reload JavaScript on page transitions with enhanced navigation

This commit is contained in:
sbwalker 2024-03-08 14:03:22 -05:00
parent 32efeee4c7
commit 7f74e79253
4 changed files with 111 additions and 6 deletions

View File

@ -580,6 +580,7 @@
ES6Module = resource.ES6Module,
Content = resource.Content,
RenderMode = resource.RenderMode,
Reload = resource.Reload,
Level = level,
Namespace = name
});

View File

@ -526,12 +526,20 @@
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") +
" src =\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
if (!resource.Reload)
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") +
" src =\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
}
else
{
// use custom element which can execute script on every page transition
return "<page-script src=\"" + resource.Url + "\"></page-script>";
}
}
else
{
@ -684,6 +692,7 @@
ES6Module = resource.ES6Module,
Content = resource.Content,
RenderMode = resource.RenderMode,
Reload = resource.Reload,
Level = level,
Namespace = name
});

View File

@ -0,0 +1,90 @@
const pageScriptInfoBySrc = new Map();
function registerPageScriptElement(src) {
if (!src) {
throw new Error('Must provide a non-empty value for the "src" attribute.');
}
let pageScriptInfo = pageScriptInfoBySrc.get(src);
if (pageScriptInfo) {
pageScriptInfo.referenceCount++;
} else {
pageScriptInfo = { referenceCount: 1, module: null };
pageScriptInfoBySrc.set(src, pageScriptInfo);
initializePageScriptModule(src, pageScriptInfo);
}
}
function unregisterPageScriptElement(src) {
if (!src) {
return;
}
const pageScriptInfo = pageScriptInfoBySrc.get(src);
if (!pageScriptInfo) {
return;
}
pageScriptInfo.referenceCount--;
}
async function initializePageScriptModule(src, pageScriptInfo) {
// If the path is relative, normalize it by by making it an absolute URL
// with document's the base HREF.
if (src.startsWith("./")) {
src = new URL(src.substr(2), document.baseURI).toString();
}
const module = await import(src);
if (pageScriptInfo.referenceCount <= 0) {
// All page-script elements with the same 'src' were
// unregistered while we were loading the module.
return;
}
pageScriptInfo.module = module;
module.onLoad?.();
module.onUpdate?.();
}
function onEnhancedLoad() {
// Start by invoking 'onDispose' on any modules that are no longer referenced.
for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
if (referenceCount <= 0) {
module?.onDispose?.();
pageScriptInfoBySrc.delete(src);
}
}
// Then invoke 'onUpdate' on the remaining modules.
for (const { module } of pageScriptInfoBySrc.values()) {
module?.onUpdate?.();
}
}
export function afterWebStarted(blazor) {
customElements.define('page-script', class extends HTMLElement {
static observedAttributes = ['src'];
// We use attributeChangedCallback instead of connectedCallback
// because a page-script element might get reused between enhanced
// navigations.
attributeChangedCallback(name, oldValue, newValue) {
if (name !== 'src') {
return;
}
this.src = newValue;
unregisterPageScriptElement(oldValue);
registerPageScriptElement(newValue);
}
disconnectedCallback() {
unregisterPageScriptElement(this.src);
}
});
blazor.addEventListener('enhancedload', onEnhancedLoad);
}

View File

@ -67,6 +67,11 @@ namespace Oqtane.Models
/// </summary>
public string RenderMode { get; set; }
/// <summary>
/// Indicates that a script should be reloaded on every page transition - not applicable to Stylesheets
/// </summary>
public bool Reload { get; set; }
/// <summary>
/// The namespace of the component that declared the resource - only used in SiteRouter
/// </summary>