commit
0296230219
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
break;
|
||||
case "HeadContent":
|
||||
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n";
|
||||
var content = FormatScripts(SiteState.Properties.HeadContent) + "\n";
|
||||
if (content != _content)
|
||||
{
|
||||
_content = content;
|
||||
|
@ -51,14 +51,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
private string RemoveScripts(string headcontent)
|
||||
private string FormatScripts(string headcontent)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(headcontent) && RenderMode == RenderModes.Interactive)
|
||||
if (!string.IsNullOrEmpty(headcontent))
|
||||
{
|
||||
var index = headcontent.IndexOf("<script");
|
||||
while (index >= 0)
|
||||
{
|
||||
headcontent = headcontent.Remove(index, headcontent.IndexOf("</script>") + 9 - index);
|
||||
var script = headcontent.Substring(index, headcontent.IndexOf("</script>") + 9 - index);
|
||||
if (RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
// remove scripts when interactive rendering
|
||||
headcontent = headcontent.Remove(index, script.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// transform scripts into page-scripts when static rendering
|
||||
var pagescript = script.Replace("script", "page-script");
|
||||
if (!pagescript.Contains("><"))
|
||||
{
|
||||
var content = pagescript.Substring(pagescript.IndexOf(">") + 1, pagescript.LastIndexOf("<") - pagescript.IndexOf(">") - 1);
|
||||
pagescript = pagescript.Replace(">" + content, " content=\"" + content.Replace("\"","'") + "\">");
|
||||
}
|
||||
headcontent = headcontent.Replace(script, pagescript);
|
||||
}
|
||||
index = headcontent.IndexOf("<script");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -547,37 +547,20 @@
|
|||
|
||||
private string CreateScript(Resource resource, Alias alias)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(resource.Url))
|
||||
var src = resource.Url;
|
||||
if (!string.IsNullOrEmpty(src))
|
||||
{
|
||||
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
|
||||
@if (string.IsNullOrEmpty(resource.Integrity) && string.IsNullOrEmpty(resource.CrossOrigin))
|
||||
{
|
||||
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||
}
|
||||
else
|
||||
{
|
||||
// use modulepreload for external resources
|
||||
return "<link rel=\"modulepreload\" href=\"" + resource.Url + "\" integrity=\"" + resource.Integrity + "\" crossorigin=\"" + resource.CrossOrigin + "\" />\n" +
|
||||
"<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// inline script
|
||||
return "<script>" + resource.Content + "</script>";
|
||||
src = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
||||
}
|
||||
|
||||
return "<page-script" +
|
||||
((!string.IsNullOrEmpty(src)) ? " src=\"" + src + "\"" : "") +
|
||||
((resource.ES6Module || resource.Reload) ? " type=\"module\"" : "") +
|
||||
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
|
||||
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
|
||||
((!string.IsNullOrEmpty(resource.Content)) ? " content=\"" + resource.Content + "\"" : "") +
|
||||
((resource.Reload) ? " reload=\"true\"" : "") +
|
||||
"></page-script>";
|
||||
}
|
||||
|
||||
private void SetLocalizationCookie(string cookieValue)
|
||||
|
|
|
@ -1,88 +1,170 @@
|
|||
const pageScriptInfoBySrc = new Map();
|
||||
|
||||
function registerPageScriptElement(src) {
|
||||
if (!src) {
|
||||
throw new Error('Must provide a non-empty value for the "src" attribute.');
|
||||
function getKey(element) {
|
||||
if (element.src !== "") {
|
||||
return element.src;
|
||||
} else {
|
||||
return element.content;
|
||||
}
|
||||
}
|
||||
|
||||
let pageScriptInfo = pageScriptInfoBySrc.get(src);
|
||||
function registerPageScriptElement(element) {
|
||||
let key = getKey(element);
|
||||
let pageScriptInfo = pageScriptInfoBySrc.get(key);
|
||||
|
||||
if (pageScriptInfo) {
|
||||
pageScriptInfo.referenceCount++;
|
||||
} else {
|
||||
pageScriptInfo = { referenceCount: 1, module: null };
|
||||
pageScriptInfoBySrc.set(src, pageScriptInfo);
|
||||
initializePageScriptModule(src, pageScriptInfo);
|
||||
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(src) {
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageScriptInfo = pageScriptInfoBySrc.get(src);
|
||||
function unregisterPageScriptElement(element) {
|
||||
const pageScriptInfo = pageScriptInfoBySrc.get(getKey(element));
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
for (const [key, pageScriptInfo] of pageScriptInfoBySrc) {
|
||||
if (pageScriptInfo.referenceCount <= 0) {
|
||||
if (pageScriptInfo.module) {
|
||||
pageScriptInfo.module.onDispose?.();
|
||||
}
|
||||
pageScriptInfoBySrc.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Then invoke 'onUpdate' on the remaining modules.
|
||||
for (const { module } of pageScriptInfoBySrc.values()) {
|
||||
module?.onUpdate?.();
|
||||
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'];
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.src = newValue;
|
||||
unregisterPageScriptElement(oldValue);
|
||||
registerPageScriptElement(newValue);
|
||||
connectedCallback() {
|
||||
registerPageScriptElement(this);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
unregisterPageScriptElement(this.src);
|
||||
unregisterPageScriptElement(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user