refactor Static Blazor script processing
This commit is contained in:
parent
3fab79afc0
commit
2435d610c7
|
@ -58,27 +58,22 @@
|
||||||
var index = headcontent.IndexOf("<script");
|
var index = headcontent.IndexOf("<script");
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
{
|
{
|
||||||
var script = headcontent.Substring(index, headcontent.IndexOf("</script>") + 9 - index);
|
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) - index + 9);
|
||||||
if (RenderMode == RenderModes.Interactive)
|
if (RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
// remove scripts when interactive rendering
|
// remove scripts when interactive rendering
|
||||||
headcontent = headcontent.Remove(index, script.Length);
|
headcontent = headcontent.Remove(index, script.Length);
|
||||||
|
index = headcontent.IndexOf("<script");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// transform scripts into page-scripts when static rendering
|
if (!script.Contains("><") && !script.Contains("data-reload"))
|
||||||
var pagescript = script.Replace("script", "page-script");
|
|
||||||
if (!pagescript.Contains("><"))
|
|
||||||
{
|
{
|
||||||
// convert inline script body to content attribute
|
// add data-reload attribute to inline script
|
||||||
var content = pagescript.Substring(pagescript.IndexOf(">") + 1, pagescript.LastIndexOf("<") - pagescript.IndexOf(">") - 1);
|
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"true\""));
|
||||||
pagescript = pagescript.Replace(">" + content, " content=\"" + content.Replace("\"","'") + "\">");
|
|
||||||
}
|
}
|
||||||
// move page-script to end of headcontent as Blazor will move anything after a page-script element to the top of the body
|
index = headcontent.IndexOf("<script", index + 1);
|
||||||
headcontent = headcontent.Replace(script, "");
|
|
||||||
headcontent = headcontent + pagescript;
|
|
||||||
}
|
}
|
||||||
index = headcontent.IndexOf("<script");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return headcontent;
|
return headcontent;
|
||||||
|
|
|
@ -56,6 +56,10 @@
|
||||||
<Head RenderMode="@_renderMode" Runtime="@_runtime" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
|
<Head RenderMode="@_renderMode" Runtime="@_runtime" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
|
||||||
}
|
}
|
||||||
@((MarkupString)_headResources)
|
@((MarkupString)_headResources)
|
||||||
|
@if (_renderMode == RenderModes.Static)
|
||||||
|
{
|
||||||
|
<page-script src="./js/reload.js"></page-script>
|
||||||
|
}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@if (string.IsNullOrEmpty(_message))
|
@if (string.IsNullOrEmpty(_message))
|
||||||
|
@ -547,31 +551,18 @@
|
||||||
|
|
||||||
private string CreateScript(Resource resource, Alias alias)
|
private string CreateScript(Resource resource, Alias alias)
|
||||||
{
|
{
|
||||||
var src = resource.Url;
|
if (!resource.Reload)
|
||||||
if (!string.IsNullOrEmpty(src))
|
|
||||||
{
|
{
|
||||||
src = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
||||||
}
|
return "<script src=\"" + url + "\"" +
|
||||||
|
((resource.ES6Module) ? " type=\"module\"" : "") +
|
||||||
if (resource.Level == ResourceLevel.Module || !string.IsNullOrEmpty(resource.Content) || resource.Reload)
|
|
||||||
{
|
|
||||||
return "<page-script" +
|
|
||||||
((!string.IsNullOrEmpty(src)) ? " src=\"" + src + "\"" : "") +
|
|
||||||
((resource.ES6Module || resource.Reload) ? " type=\"module\"" : "") +
|
|
||||||
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
|
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
|
||||||
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
|
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
|
||||||
((!string.IsNullOrEmpty(resource.Content)) ? " content=\"" + resource.Content + "\"" : "") +
|
"></script>";
|
||||||
((resource.Reload) ? " reload=\"true\"" : "") +
|
|
||||||
"></page-script>";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return "<script" +
|
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||||
" src=\"" + src + "\"" +
|
|
||||||
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
|
|
||||||
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
|
|
||||||
((resource.ES6Module) ? " type=\"module\"" : "") +
|
|
||||||
"></script>";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,194 +1,88 @@
|
||||||
const pageScriptInfoBySrc = new Map();
|
const pageScriptInfoBySrc = new Map();
|
||||||
|
|
||||||
function getKey(element) {
|
function registerPageScriptElement(src) {
|
||||||
if (element.src !== "") {
|
if (!src) {
|
||||||
return element.src;
|
throw new Error('Must provide a non-empty value for the "src" attribute.');
|
||||||
} else {
|
|
||||||
return element.content;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerPageScript(element) {
|
let pageScriptInfo = pageScriptInfoBySrc.get(src);
|
||||||
let key = getKey(element);
|
|
||||||
let pageScriptInfo = pageScriptInfoBySrc.get(key);
|
|
||||||
|
|
||||||
if (pageScriptInfo) {
|
if (pageScriptInfo) {
|
||||||
pageScriptInfo.referenceCount++;
|
pageScriptInfo.referenceCount++;
|
||||||
} else {
|
} else {
|
||||||
if (element.src.startsWith("./")) {
|
pageScriptInfo = { referenceCount: 1, module: null };
|
||||||
element.src = new URL(element.src.substr(2), document.baseURI).toString();
|
pageScriptInfoBySrc.set(src, pageScriptInfo);
|
||||||
}
|
initializePageScriptModule(src, pageScriptInfo);
|
||||||
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(key, pageScriptInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initializePageScript(key, pageScriptInfo) {
|
function unregisterPageScriptElement(src) {
|
||||||
if (pageScriptInfo.reload) {
|
if (!src) {
|
||||||
const module = await import(pageScriptInfo.src);
|
return;
|
||||||
pageScriptInfo.module = module;
|
|
||||||
module.onLoad?.();
|
|
||||||
module.onUpdate?.();
|
|
||||||
} else {
|
|
||||||
if (!scriptExists(pageScriptInfo)) {
|
|
||||||
try {
|
|
||||||
injectScript(pageScriptInfo);
|
|
||||||
} catch (error) {
|
|
||||||
if (pageScriptInfo.src !== "") {
|
|
||||||
console.error("Failed to load external script: ${pageScriptInfo.src}", error);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to load inline script: ${pageScriptInfo.content}", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
removePageScript(key, pageScriptInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnhancedLoad() {
|
const pageScriptInfo = pageScriptInfoBySrc.get(src);
|
||||||
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 {
|
|
||||||
if (!scriptExists(pageScriptInfo)) {
|
|
||||||
try {
|
|
||||||
injectScript(pageScriptInfo);
|
|
||||||
} catch (error) {
|
|
||||||
if (pageScriptInfo.src !== "") {
|
|
||||||
console.error("Failed to load external script: ${pageScriptInfo.src}", error);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to load inline script: ${pageScriptInfo.content}", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, pageScriptInfo] of pageScriptInfoBySrc) {
|
|
||||||
removePageScript(key, pageScriptInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function scriptExists(pageScriptInfo) {
|
|
||||||
if (pageScriptInfo.src !== "") {
|
|
||||||
return document.querySelector("script[src=\"" + pageScriptInfo.src + "\"]");
|
|
||||||
} else {
|
|
||||||
const scripts = document.querySelectorAll('script:not([src])');
|
|
||||||
for (let i = 0; i < scripts.length; i++) {
|
|
||||||
if (scripts[i].textContent.includes(pageScriptInfo.content)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectScript(pageScriptInfo) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var script = document.createElement("script");
|
|
||||||
|
|
||||||
script.async = false;
|
|
||||||
if (pageScriptInfo.type !== "") {
|
|
||||||
script.type = pageScriptInfo.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageScriptInfo.src !== "") {
|
|
||||||
script.src = pageScriptInfo.src;
|
|
||||||
if (pageScriptInfo.integrity !== "") {
|
|
||||||
script.integrity = pageScriptInfo.integrity;
|
|
||||||
}
|
|
||||||
if (pageScriptInfo.crossorigin !== "") {
|
|
||||||
script.crossOrigin = pageScriptInfo.crossorigin;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
script.innerHTML = pageScriptInfo.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
script.onload = () => resolve();
|
|
||||||
script.onerror = (error) => reject(error);
|
|
||||||
|
|
||||||
// add script to page
|
|
||||||
document.head.appendChild(script);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePageScript(key, pageScriptInfo) {
|
|
||||||
var pageScript;
|
|
||||||
|
|
||||||
if (pageScriptInfo.src !== "") {
|
|
||||||
pageScript = document.querySelector("page-script[src=\"" + key + "\"]");
|
|
||||||
} else {
|
|
||||||
pageScript = document.querySelector("page-script[content=\"" + CSS.escape(pageScriptInfo.content) + "\"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageScript) {
|
|
||||||
pageScript.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregisterPageScript(element) {
|
|
||||||
const pageScriptInfo = pageScriptInfoBySrc.get(getKey(element));
|
|
||||||
if (!pageScriptInfo) {
|
if (!pageScriptInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageScriptInfo.referenceCount--;
|
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) {
|
export function afterWebStarted(blazor) {
|
||||||
customElements.define('page-script', class extends HTMLElement {
|
customElements.define('page-script', class extends HTMLElement {
|
||||||
static observedAttributes = ['src', 'type', 'integrity', 'crossorigin', 'content', 'reload'];
|
static observedAttributes = ['src'];
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.src = "";
|
|
||||||
this.type = "";
|
|
||||||
this.integrity = "";
|
|
||||||
this.crossorigin = "";
|
|
||||||
this.content = "";
|
|
||||||
this.reload = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// We use attributeChangedCallback instead of connectedCallback
|
||||||
|
// because a page-script element might get reused between enhanced
|
||||||
|
// navigations.
|
||||||
attributeChangedCallback(name, oldValue, newValue) {
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
switch (name) {
|
if (name !== 'src') {
|
||||||
case "src":
|
return;
|
||||||
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 "reload":
|
|
||||||
this.reload = newValue;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if last attribute for element has been processed
|
this.src = newValue;
|
||||||
if (this.attributes[this.attributes.length - 1].name === name) {
|
unregisterPageScriptElement(oldValue);
|
||||||
registerPageScript(this);
|
registerPageScriptElement(newValue);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
unregisterPageScript(this);
|
unregisterPageScriptElement(this.src);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
87
Oqtane.Server/wwwroot/js/reload.js
Normal file
87
Oqtane.Server/wwwroot/js/reload.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
const scriptInfoBySrc = new Map();
|
||||||
|
|
||||||
|
function getKey(script) {
|
||||||
|
if (script.hasAttribute("src") && script.src !== "") {
|
||||||
|
return script.src;
|
||||||
|
} 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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove scripts that are no longer referenced
|
||||||
|
for (const [key, scriptInfo] of scriptInfoBySrc) {
|
||||||
|
if (scriptInfo.timestamp !== timestamp) {
|
||||||
|
scriptInfoBySrc.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 replaceScript(script) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var newScript = document.createElement("script");
|
||||||
|
|
||||||
|
newScript.async = false;
|
||||||
|
if (script.type !== "") {
|
||||||
|
newScript.type = script.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script.src !== "") {
|
||||||
|
newScript.src = script.src;
|
||||||
|
if (script.integrity !== "") {
|
||||||
|
newScript.integrity = script.integrity;
|
||||||
|
}
|
||||||
|
if (script.crossorigin !== "") {
|
||||||
|
newScript.crossOrigin = script.crossOrigin;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newScript.innerHTML = script.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
newScript.onload = () => resolve();
|
||||||
|
newScript.onerror = (error) => reject(error);
|
||||||
|
|
||||||
|
// remove existing newScript script
|
||||||
|
script.remove();
|
||||||
|
|
||||||
|
// replace with new newScript to force reload in Blazor
|
||||||
|
document.head.appendChild(newScript);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user