changes to support page level scripts, ability to detect prerendering

This commit is contained in:
sbwalker 2023-05-22 13:56:48 -04:00
parent e41d9008b3
commit ded326c822
7 changed files with 131 additions and 160 deletions

View File

@ -1,6 +1,8 @@
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject SiteState SiteState @inject SiteState SiteState
@inject IServiceProvider ServiceProvider
@if (_initialized) @if (_initialized)
{ {
@ -54,12 +56,24 @@
private PageState PageState { get; set; } private PageState PageState { get; set; }
private IHttpContextAccessor accessor;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
SiteState.RemoteIPAddress = RemoteIPAddress; SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken; SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken; SiteState.AuthorizationToken = AuthorizationToken;
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
if (accessor != null)
{
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
}
else
{
SiteState.IsPrerendering = true;
}
_installation = await InstallationService.IsInstalled(); _installation = await InstallationService.IsInstalled();
if (_installation.Alias != null) if (_installation.Alias != null)
{ {

View File

@ -278,6 +278,7 @@ namespace Oqtane.Modules
SiteState.Properties.PageTitle = title; SiteState.Properties.PageTitle = title;
} }
// note - only supports links and meta tags - not scripts
public void AddHeadContent(string content) public void AddHeadContent(string content)
{ {
if (string.IsNullOrEmpty(SiteState.Properties.HeadContent)) if (string.IsNullOrEmpty(SiteState.Properties.HeadContent))

View File

@ -38,21 +38,21 @@
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value); favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value);
} }
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/x-icon\" href=\"{favicon}\" />\n"; headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/x-icon\" href=\"{favicon}\" />\n";
// head content
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
{
headcontent += ProcessHeadContent(PageState.Site.HeadContent, "site") + "\n";
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent))
{
headcontent += ProcessHeadContent(PageState.Page.HeadContent, $"page{PageState.Page.PageId}") + "\n";
}
// stylesheets // stylesheets
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
headcontent += CreateLink(url, resource.Integrity, resource.CrossOrigin) + "\n"; headcontent += CreateLink(url, resource.Integrity, resource.CrossOrigin) + "\n";
} }
// head content
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
{
headcontent += PageState.Site.HeadContent + "\n";
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent))
{
headcontent += PageState.Page.HeadContent + "\n";
}
SiteState.Properties.HeadContent = headcontent; SiteState.Properties.HeadContent = headcontent;
DynamicComponent = builder => DynamicComponent = builder =>
@ -64,16 +64,20 @@
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{ {
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script")) if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
{ {
// inject scripts into page // inject scripts into page dynamically
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var scripts = new List<object>();
var count = 0; var count = 0;
var index = PageState.Page.HeadContent.IndexOf("<script"); var index = PageState.Page.HeadContent.IndexOf("<script");
while (index >= 0) while (index >= 0)
{ {
var script = PageState.Page.HeadContent.Substring(index, PageState.Page.HeadContent.IndexOf("</script>", index) + 9 - index); var script = PageState.Page.HeadContent.Substring(index, PageState.Page.HeadContent.IndexOf("</script>", index) + 9 - index);
// get script attributes
var attributes = script.Substring(0, script.IndexOf(">")).Replace("\"", "").Split(" "); var attributes = script.Substring(0, script.IndexOf(">")).Replace("\"", "").Split(" ");
string id = ""; string id = "";
string src = ""; string src = "";
@ -105,13 +109,15 @@
} }
} }
} }
// inject script
if (!string.IsNullOrEmpty(src)) if (!string.IsNullOrEmpty(src))
{ {
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src; src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
await interop.IncludeScript(id, src, integrity, crossorigin, type, "", "head"); scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = "head" });
} }
else else
{ {
// inline script must have an id attribute
if (id == "") if (id == "")
{ {
count += 1; count += 1;
@ -122,30 +128,12 @@
} }
index = PageState.Page.HeadContent.IndexOf("<script", index + 1); index = PageState.Page.HeadContent.IndexOf("<script", index + 1);
} }
} if (scripts.Any())
}
private string ProcessHeadContent(string headcontent, string id)
{ {
// iterate scripts await interop.IncludeScripts(scripts.ToArray());
if (!string.IsNullOrEmpty(headcontent)) }
{
var count = 0;
var index = headcontent.IndexOf("<script");
while (index >= 0)
{
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index);
if (!script.Contains("src=") && !script.Contains("id="))
{
count += 1;
id += $"-script{count}";
headcontent = headcontent.Replace(script, script.Replace("<script", $"<script id=\"{id}\""));
index += id.Length;
}
index = headcontent.IndexOf("<script", index + 1);
} }
} }
return headcontent;
} }
private string CreateLink(string url, string integrity, string crossorigin) private string CreateLink(string url, string integrity, string crossorigin)

View File

@ -15,8 +15,8 @@
{ {
<link id="app-manifest" rel="manifest" /> <link id="app-manifest" rel="manifest" />
} }
@Html.Raw(Model.HeadResources)
<component type="typeof(Oqtane.Head)" render-mode="@((RenderMode)Enum.Parse(typeof(RenderMode), Model.RenderMode, true))" /> <component type="typeof(Oqtane.Head)" render-mode="@((RenderMode)Enum.Parse(typeof(RenderMode), Model.RenderMode, true))" />
@Html.Raw(Model.HeadResources)
</head> </head>
<body> <body>
@if (string.IsNullOrEmpty(Model.Message)) @if (string.IsNullOrEmpty(Model.Message))

View File

@ -19,7 +19,6 @@ using Microsoft.Extensions.Primitives;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Themes;
namespace Oqtane.Pages namespace Oqtane.Pages
{ {
@ -130,10 +129,8 @@ namespace Oqtane.Pages
{ {
PWAScript = CreatePWAScript(alias, site, route); PWAScript = CreatePWAScript(alias, site, route);
} }
if (!string.IsNullOrEmpty(site.HeadContent)) // site level scripts
{ HeadResources += ParseScripts(site.HeadContent);
ProcessHeadContent(site.HeadContent, "site");
}
// get jwt token for downstream APIs // get jwt token for downstream APIs
if (User.Identity.IsAuthenticated) if (User.Identity.IsAuthenticated)
@ -174,8 +171,6 @@ namespace Oqtane.Pages
} }
} }
ProcessHeadContent(page.HeadContent, $"page{page.PageId}");
// include global resources // include global resources
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
@ -427,27 +422,20 @@ namespace Oqtane.Pages
} }
} }
private void ProcessHeadContent(string headcontent, string id) private string ParseScripts(string headcontent)
{ {
// add scripts to page // iterate scripts
var scripts = "";
if (!string.IsNullOrEmpty(headcontent)) if (!string.IsNullOrEmpty(headcontent))
{ {
var count = 0;
var index = headcontent.IndexOf("<script"); var index = headcontent.IndexOf("<script");
while (index >= 0) while (index >= 0)
{ {
var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index); scripts += headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index);
if (!script.Contains("src=") && !script.Contains("id="))
{
count += 1;
id += $"-script{count}";
script = script.Replace("<script", $"<script id=\"{id}\"");
index += id.Length;
}
HeadResources += script + Environment.NewLine;
index = headcontent.IndexOf("<script", index + 1); index = headcontent.IndexOf("<script", index + 1);
} }
} }
return scripts;
} }
private void ProcessResource(Resource resource, int count, Alias alias) private void ProcessResource(Resource resource, int count, Alias alias)

View File

@ -116,6 +116,10 @@ Oqtane.Interop = {
else { else {
script = document.getElementById(id); script = document.getElementById(id);
} }
if (script !== null) {
script.remove();
script = null;
}
if (script === null) { if (script === null) {
script = document.createElement("script"); script = document.createElement("script");
if (id !== "") { if (id !== "") {
@ -139,49 +143,21 @@ Oqtane.Interop = {
script.async = false; script.async = false;
this.addScript(script, location) this.addScript(script, location)
.then(() => { .then(() => {
if (src !== "") {
console.log(src + ' loaded'); console.log(src + ' loaded');
}
else {
console.log(id + ' loaded');
}
}) })
.catch(() => { .catch(() => {
console.error(src + ' failed');
});
}
else {
if (script.id !== id) {
script.setAttribute('id', id);
}
if (type !== "") {
if (script.type !== type) {
script.setAttribute('type', type);
}
} else {
script.removeAttribute('type');
}
if (src !== "") { if (src !== "") {
if (script.src !== this.getAbsoluteUrl(src)) { console.error(src + ' failed');
script.removeAttribute('integrity');
script.removeAttribute('crossorigin');
script.src = src;
}
if (integrity !== "") {
if (script.integrity !== integrity) {
script.setAttribute('integrity', integrity);
}
} else {
script.removeAttribute('integrity');
}
if (crossorigin !== "") {
if (script.crossOrigin !== crossorigin) {
script.setAttribute('crossorigin', crossorigin);
}
} else {
script.removeAttribute('crossorigin');
}
} }
else { else {
if (script.innerHTML !== content) { console.error(id + ' failed');
script.innerHTML = content;
}
} }
});
} }
}, },
addScript: function (script, location) { addScript: function (script, location) {
@ -234,6 +210,10 @@ Oqtane.Interop = {
if (path === scripts[s].href && scripts[s].es6module === true) { if (path === scripts[s].href && scripts[s].es6module === true) {
element.type = "module"; element.type = "module";
} }
if (path === scripts[s].href && scripts[s].location === 'body') {
document.body.appendChild(element);
return false; // return false to bypass default DOM insertion mechanism
}
} }
} }
}) })

View File

@ -9,7 +9,7 @@ namespace Oqtane.Shared
public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client
public string AuthorizationToken { get; set; } // passed from server for use in service calls on client public string AuthorizationToken { get; set; } // passed from server for use in service calls on client
public string RemoteIPAddress { get; set; } // passed from server as cannot be reliably retrieved on client public string RemoteIPAddress { get; set; } // passed from server as cannot be reliably retrieved on client
public bool IsPrerendering{ get; set; }
private dynamic _properties; private dynamic _properties;
public dynamic Properties => _properties ?? (_properties = new PropertyDictionary()); public dynamic Properties => _properties ?? (_properties = new PropertyDictionary());