diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor
index 316a10c9..4a606397 100644
--- a/Oqtane.Client/UI/SiteRouter.razor
+++ b/Oqtane.Client/UI/SiteRouter.razor
@@ -580,6 +580,7 @@
ES6Module = resource.ES6Module,
Content = resource.Content,
RenderMode = resource.RenderMode,
+ Reload = resource.Reload,
Level = level,
Namespace = name
});
diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor
index be303c8d..4d35946c 100644
--- a/Oqtane.Server/Components/App.razor
+++ b/Oqtane.Server/Components/App.razor
@@ -526,12 +526,20 @@
{
if (!string.IsNullOrEmpty(resource.Url))
{
- var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
- return ""; // 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 ""; // src at end of element due to enhanced navigation patch algorithm
+ }
+ else
+ {
+ // use custom element which can execute script on every page transition
+ return "";
+ }
}
else
{
@@ -684,6 +692,7 @@
ES6Module = resource.ES6Module,
Content = resource.Content,
RenderMode = resource.RenderMode,
+ Reload = resource.Reload,
Level = level,
Namespace = name
});
diff --git a/Oqtane.Server/wwwroot/Oqtane.Server.lib.module.js b/Oqtane.Server/wwwroot/Oqtane.Server.lib.module.js
new file mode 100644
index 00000000..508f42a0
--- /dev/null
+++ b/Oqtane.Server/wwwroot/Oqtane.Server.lib.module.js
@@ -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);
+}
\ No newline at end of file
diff --git a/Oqtane.Shared/Models/Resource.cs b/Oqtane.Shared/Models/Resource.cs
index 13680a0d..8092256b 100644
--- a/Oqtane.Shared/Models/Resource.cs
+++ b/Oqtane.Shared/Models/Resource.cs
@@ -67,6 +67,11 @@ namespace Oqtane.Models
///
public string RenderMode { get; set; }
+ ///
+ /// Indicates that a script should be reloaded on every page transition - not applicable to Stylesheets
+ ///
+ public bool Reload { get; set; }
+
///
/// The namespace of the component that declared the resource - only used in SiteRouter
///