Require <script type="blazor-boot"> to define script injection location
This commit is contained in:
parent
d6d7a5b1c5
commit
b38718d77f
|
|
@ -6,5 +6,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
<script type="blazor-boot"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
|
||||
<p id="loadingIndicator">Loading...</p>
|
||||
|
||||
<script type="blazor-boot"></script>
|
||||
<script src="loader.js"></script>
|
||||
<script>
|
||||
initMono(['/_framework/_bin/MonoSanityClient.dll'], function () {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<title>Blazor standalone</title>
|
||||
</head>
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
<app>Loading...</app>
|
||||
<script type="blazor-boot"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import './GlobalExports';
|
|||
async function boot() {
|
||||
// Read startup config from the <script> element that's importing this file
|
||||
const allScriptElems = document.getElementsByTagName('script');
|
||||
const thisScriptElem = allScriptElems[allScriptElems.length - 1];
|
||||
const thisScriptElem = document.currentScript || allScriptElems[allScriptElems.length - 1];
|
||||
const entryPoint = thisScriptElem.getAttribute('main');
|
||||
if (!entryPoint) {
|
||||
throw new Error('Missing "main" attribute on Blazor script tag.');
|
||||
throw new Error('Missing "main" attribute on Blazor Config script tag.');
|
||||
}
|
||||
const entryPointAssemblyName = getAssemblyNameFromUrl(entryPoint);
|
||||
const referenceAssembliesCommaSeparated = thisScriptElem.getAttribute('references') || '';
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Linq;
|
||||
using AngleSharp.Parser.Html;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
||||
{
|
||||
|
|
@ -27,26 +30,58 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Injects the Blazor boot code and supporting config data at a user-designated
|
||||
/// script tag identified with a <c>type</c> of <c>blazor-boot</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If a matching script tag is found, then it will be adjusted to inject
|
||||
/// supporting configuration data, including a <c>src</c> attribute that
|
||||
/// will load the Blazor client-side library. Any existing attribute
|
||||
/// names that match the boot config data will be overwritten, but other
|
||||
/// user-supplied attributes will be left intact. This allows, for example,
|
||||
/// to designate asynchronous loading or deferred running of the script
|
||||
/// reference.
|
||||
/// </para><para>
|
||||
/// If no matching script tag is found, it is assumed that the user is
|
||||
/// responsible for completing the Blazor boot process.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static string GetIndexHtmlContents(string htmlTemplate, string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||
{
|
||||
// TODO: Instead of inserting the script as the first element in <body>,
|
||||
// consider either:
|
||||
// [1] Inserting it just before the first <script> in the <body>, so that
|
||||
// developers can still put other <script> elems after (and therefore
|
||||
// reference Blazor JS APIs from them) but we don't block the page
|
||||
// rendering while fetching that script. Note that adding async/defer
|
||||
// alone isn't enough because that doesn't help older browsers that
|
||||
// don't suppor them.
|
||||
// [2] Or possibly better, don't insert the <script> magically at all.
|
||||
// Instead, just insert a block of configuration data at the top of
|
||||
// <body> (e.g., <script type='blazor-config'>{ ...json... }</script>)
|
||||
// and then let the developer manually place the tag that loads blazor.js
|
||||
// wherever they want (adding their own async/defer if they want).
|
||||
return htmlTemplate
|
||||
.Replace("<body>", "<body>\n" + CreateBootMarkup(assemblyName, binFiles));
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.Parse(htmlTemplate);
|
||||
|
||||
// First see if the user has declared a 'boot' script,
|
||||
// then it's their responsibility to load blazor.js
|
||||
var bootScript = dom.Body?.QuerySelectorAll("script")
|
||||
.Where(x => x.Attributes["type"]?.Value == "blazor-boot").FirstOrDefault();
|
||||
|
||||
// If we find a script tag that is decorated with a type="blazor-boot"
|
||||
// this will be the point at which we start the Blazor boot process
|
||||
if (bootScript != null)
|
||||
{
|
||||
// We need to remove the 'type="blazor-boot"' so that
|
||||
// it reverts to being processed as JS by the browser
|
||||
bootScript.RemoveAttribute("type");
|
||||
|
||||
// Leave any user-specified attributes on the tag as-is
|
||||
// and add/overwrite the config data needed to boot Blazor
|
||||
InjectBootConfig(bootScript, assemblyName, binFiles);
|
||||
}
|
||||
|
||||
// If no blazor-boot script tag was found, we skip it and
|
||||
// leave it up to the user to handle kicking off the boot
|
||||
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
dom.ToHtml(writer, new AutoSelectedMarkupFormatter());
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateBootMarkup(string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||
private static void InjectBootConfig(IElement script, string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||
{
|
||||
var assemblyNameWithExtension = $"{assemblyName}.dll";
|
||||
var referenceNames = binFiles
|
||||
|
|
@ -54,9 +89,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
|||
.Select(file => file.Name);
|
||||
var referencesAttribute = string.Join(",", referenceNames.ToArray());
|
||||
|
||||
return $"<script src=\"/_framework/blazor.js\"" +
|
||||
$" main=\"{assemblyNameWithExtension}\"" +
|
||||
$" references=\"{referencesAttribute}\"></script>";
|
||||
script.SetAttribute("src", "/_framework/blazor.js");
|
||||
script.SetAttribute("main", assemblyNameWithExtension);
|
||||
script.SetAttribute("references", referencesAttribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
{
|
||||
// Arrange
|
||||
var htmlTemplate = "test";
|
||||
var htmlOutput = $"<html><head></head><body>{htmlTemplate}</body></html>";
|
||||
var instance = new IndexHtmlFileProvider(
|
||||
htmlTemplate, "fakeassembly", Enumerable.Empty<IFileInfo>());
|
||||
|
||||
|
|
@ -43,8 +44,8 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
Assert.False(file.IsDirectory);
|
||||
Assert.Equal("/index.html", file.PhysicalPath);
|
||||
Assert.Equal("index.html", file.Name);
|
||||
Assert.Equal(htmlTemplate, ReadString(file));
|
||||
Assert.Equal(htmlTemplate.Length, file.Length);
|
||||
Assert.Equal(htmlOutput, ReadString(file));
|
||||
Assert.Equal(htmlOutput.Length, file.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -65,7 +66,36 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertsScriptTagReferencingAssemblyAndDependencies()
|
||||
public void InjectsScriptTagReferencingAssemblyAndDependencies()
|
||||
{
|
||||
// Arrange
|
||||
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body><script type='blazor-boot'></script></html>";
|
||||
var dependencies = new IFileInfo[]
|
||||
{
|
||||
new TestFileInfo("System.Abc.dll"),
|
||||
new TestFileInfo("MyApp.ClassLib.dll"),
|
||||
};
|
||||
var instance = new IndexHtmlFileProvider(
|
||||
htmlTemplate, "MyApp.Entrypoint", dependencies);
|
||||
|
||||
// Act
|
||||
var file = instance.GetFileInfo("/index.html");
|
||||
var parsedHtml = new HtmlParser().Parse(ReadString(file));
|
||||
var firstElem = parsedHtml.Body.FirstElementChild;
|
||||
var scriptElem = parsedHtml.Body.QuerySelector("script");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("h1", firstElem.TagName.ToLowerInvariant());
|
||||
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());
|
||||
Assert.False(scriptElem.HasChildNodes);
|
||||
Assert.Equal("/_framework/blazor.js", scriptElem.GetAttribute("src"));
|
||||
Assert.Equal("MyApp.Entrypoint.dll", scriptElem.GetAttribute("main"));
|
||||
Assert.Equal("System.Abc.dll,MyApp.ClassLib.dll", scriptElem.GetAttribute("references"));
|
||||
Assert.False(scriptElem.HasAttribute("type"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingBootScriptTagReferencingAssemblyAndDependencies()
|
||||
{
|
||||
// Arrange
|
||||
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body></html>";
|
||||
|
|
@ -80,14 +110,13 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
// Act
|
||||
var file = instance.GetFileInfo("/index.html");
|
||||
var parsedHtml = new HtmlParser().Parse(ReadString(file));
|
||||
var scriptElem = parsedHtml.Body.FirstElementChild;
|
||||
var firstElem = parsedHtml.Body.FirstElementChild;
|
||||
var scriptElem = parsedHtml.Body.QuerySelector("script");
|
||||
|
||||
|
||||
// Assert
|
||||
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());
|
||||
Assert.False(scriptElem.HasChildNodes);
|
||||
Assert.Equal("/_framework/blazor.js", scriptElem.GetAttribute("src"));
|
||||
Assert.Equal("MyApp.Entrypoint.dll", scriptElem.GetAttribute("main"));
|
||||
Assert.Equal("System.Abc.dll,MyApp.ClassLib.dll", scriptElem.GetAttribute("references"));
|
||||
Assert.Equal("h1", firstElem.TagName.ToLowerInvariant());
|
||||
Assert.Null(scriptElem);
|
||||
}
|
||||
|
||||
private static string ReadString(IFileInfo file)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<body>
|
||||
<app>Loading...</app>
|
||||
|
||||
<script type="blazor-boot"></script>
|
||||
<script>
|
||||
// The client-side .NET code calls this when it is ready to be called from test code
|
||||
// The Xunit test code polls until it sees the flag is set
|
||||
|
|
|
|||
Loading…
Reference in New Issue