Require <script type="blazor-boot"> to define script injection location

This commit is contained in:
Eugene Bekker 2018-02-09 09:33:16 -05:00 committed by Steve Sanderson
parent d6d7a5b1c5
commit b38718d77f
7 changed files with 99 additions and 31 deletions

View File

@ -6,5 +6,6 @@
</head>
<body>
<app>Loading...</app>
<script type="blazor-boot"></script>
</body>
</html>

View File

@ -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 () {

View File

@ -5,6 +5,7 @@
<title>Blazor standalone</title>
</head>
<body>
<app>Loading...</app>
<app>Loading...</app>
<script type="blazor-boot"></script>
</body>
</html>

View File

@ -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') || '';

View File

@ -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);
}
}
}

View File

@ -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)

View 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