Require <script type="blazor-boot"> to define script injection location
This commit is contained in:
parent
d6d7a5b1c5
commit
b38718d77f
|
|
@ -6,5 +6,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app>Loading...</app>
|
<app>Loading...</app>
|
||||||
|
<script type="blazor-boot"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
|
|
||||||
<p id="loadingIndicator">Loading...</p>
|
<p id="loadingIndicator">Loading...</p>
|
||||||
|
|
||||||
|
<script type="blazor-boot"></script>
|
||||||
<script src="loader.js"></script>
|
<script src="loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
initMono(['/_framework/_bin/MonoSanityClient.dll'], function () {
|
initMono(['/_framework/_bin/MonoSanityClient.dll'], function () {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<title>Blazor standalone</title>
|
<title>Blazor standalone</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app>Loading...</app>
|
<app>Loading...</app>
|
||||||
|
<script type="blazor-boot"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ import './GlobalExports';
|
||||||
async function boot() {
|
async function boot() {
|
||||||
// Read startup config from the <script> element that's importing this file
|
// Read startup config from the <script> element that's importing this file
|
||||||
const allScriptElems = document.getElementsByTagName('script');
|
const allScriptElems = document.getElementsByTagName('script');
|
||||||
const thisScriptElem = allScriptElems[allScriptElems.length - 1];
|
const thisScriptElem = document.currentScript || allScriptElems[allScriptElems.length - 1];
|
||||||
const entryPoint = thisScriptElem.getAttribute('main');
|
const entryPoint = thisScriptElem.getAttribute('main');
|
||||||
if (!entryPoint) {
|
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 entryPointAssemblyName = getAssemblyNameFromUrl(entryPoint);
|
||||||
const referenceAssembliesCommaSeparated = thisScriptElem.getAttribute('references') || '';
|
const referenceAssembliesCommaSeparated = thisScriptElem.getAttribute('references') || '';
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using AngleSharp.Parser.Html;
|
||||||
|
using AngleSharp.Dom;
|
||||||
|
using AngleSharp;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
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)
|
private static string GetIndexHtmlContents(string htmlTemplate, string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||||
{
|
{
|
||||||
// TODO: Instead of inserting the script as the first element in <body>,
|
var parser = new HtmlParser();
|
||||||
// consider either:
|
var dom = parser.Parse(htmlTemplate);
|
||||||
// [1] Inserting it just before the first <script> in the <body>, so that
|
|
||||||
// developers can still put other <script> elems after (and therefore
|
// First see if the user has declared a 'boot' script,
|
||||||
// reference Blazor JS APIs from them) but we don't block the page
|
// then it's their responsibility to load blazor.js
|
||||||
// rendering while fetching that script. Note that adding async/defer
|
var bootScript = dom.Body?.QuerySelectorAll("script")
|
||||||
// alone isn't enough because that doesn't help older browsers that
|
.Where(x => x.Attributes["type"]?.Value == "blazor-boot").FirstOrDefault();
|
||||||
// don't suppor them.
|
|
||||||
// [2] Or possibly better, don't insert the <script> magically at all.
|
// If we find a script tag that is decorated with a type="blazor-boot"
|
||||||
// Instead, just insert a block of configuration data at the top of
|
// this will be the point at which we start the Blazor boot process
|
||||||
// <body> (e.g., <script type='blazor-config'>{ ...json... }</script>)
|
if (bootScript != null)
|
||||||
// and then let the developer manually place the tag that loads blazor.js
|
{
|
||||||
// wherever they want (adding their own async/defer if they want).
|
// We need to remove the 'type="blazor-boot"' so that
|
||||||
return htmlTemplate
|
// it reverts to being processed as JS by the browser
|
||||||
.Replace("<body>", "<body>\n" + CreateBootMarkup(assemblyName, binFiles));
|
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 assemblyNameWithExtension = $"{assemblyName}.dll";
|
||||||
var referenceNames = binFiles
|
var referenceNames = binFiles
|
||||||
|
|
@ -54,9 +89,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
||||||
.Select(file => file.Name);
|
.Select(file => file.Name);
|
||||||
var referencesAttribute = string.Join(",", referenceNames.ToArray());
|
var referencesAttribute = string.Join(",", referenceNames.ToArray());
|
||||||
|
|
||||||
return $"<script src=\"/_framework/blazor.js\"" +
|
script.SetAttribute("src", "/_framework/blazor.js");
|
||||||
$" main=\"{assemblyNameWithExtension}\"" +
|
script.SetAttribute("main", assemblyNameWithExtension);
|
||||||
$" references=\"{referencesAttribute}\"></script>";
|
script.SetAttribute("references", referencesAttribute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var htmlTemplate = "test";
|
var htmlTemplate = "test";
|
||||||
|
var htmlOutput = $"<html><head></head><body>{htmlTemplate}</body></html>";
|
||||||
var instance = new IndexHtmlFileProvider(
|
var instance = new IndexHtmlFileProvider(
|
||||||
htmlTemplate, "fakeassembly", Enumerable.Empty<IFileInfo>());
|
htmlTemplate, "fakeassembly", Enumerable.Empty<IFileInfo>());
|
||||||
|
|
||||||
|
|
@ -43,8 +44,8 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
||||||
Assert.False(file.IsDirectory);
|
Assert.False(file.IsDirectory);
|
||||||
Assert.Equal("/index.html", file.PhysicalPath);
|
Assert.Equal("/index.html", file.PhysicalPath);
|
||||||
Assert.Equal("index.html", file.Name);
|
Assert.Equal("index.html", file.Name);
|
||||||
Assert.Equal(htmlTemplate, ReadString(file));
|
Assert.Equal(htmlOutput, ReadString(file));
|
||||||
Assert.Equal(htmlTemplate.Length, file.Length);
|
Assert.Equal(htmlOutput.Length, file.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -65,7 +66,36 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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
|
// Arrange
|
||||||
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body></html>";
|
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body></html>";
|
||||||
|
|
@ -80,14 +110,13 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
||||||
// Act
|
// Act
|
||||||
var file = instance.GetFileInfo("/index.html");
|
var file = instance.GetFileInfo("/index.html");
|
||||||
var parsedHtml = new HtmlParser().Parse(ReadString(file));
|
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
|
||||||
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());
|
Assert.Equal("h1", firstElem.TagName.ToLowerInvariant());
|
||||||
Assert.False(scriptElem.HasChildNodes);
|
Assert.Null(scriptElem);
|
||||||
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"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ReadString(IFileInfo file)
|
private static string ReadString(IFileInfo file)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<body>
|
<body>
|
||||||
<app>Loading...</app>
|
<app>Loading...</app>
|
||||||
|
|
||||||
|
<script type="blazor-boot"></script>
|
||||||
<script>
|
<script>
|
||||||
// The client-side .NET code calls this when it is ready to be called from test code
|
// 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
|
// The Xunit test code polls until it sees the flag is set
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue