In IndexHtmlFileProvider, preserve original source formatting
This commit is contained in:
parent
b38718d77f
commit
7bb4bbbe5c
|
|
@ -2,14 +2,14 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Linq;
|
||||
using AngleSharp.Parser.Html;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Html;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
||||
{
|
||||
|
|
@ -50,38 +50,81 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
|||
/// </remarks>
|
||||
private static string GetIndexHtmlContents(string htmlTemplate, string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.Parse(htmlTemplate);
|
||||
var resultBuilder = new StringBuilder();
|
||||
|
||||
// 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)
|
||||
// Search for a tag of the form <script type="boot-blazor"></script>, and replace
|
||||
// it with a fully-configured Blazor boot script tag
|
||||
var tokenizer = new HtmlTokenizer(
|
||||
new TextSource(htmlTemplate),
|
||||
HtmlEntityService.Resolver);
|
||||
var currentRangeStartPos = 0;
|
||||
var isInBlazorBootTag = false;
|
||||
var resumeOnNextToken = false;
|
||||
while (true)
|
||||
{
|
||||
// We need to remove the 'type="blazor-boot"' so that
|
||||
// it reverts to being processed as JS by the browser
|
||||
bootScript.RemoveAttribute("type");
|
||||
var token = tokenizer.Get();
|
||||
if (resumeOnNextToken)
|
||||
{
|
||||
resumeOnNextToken = false;
|
||||
currentRangeStartPos = token.Position.Position;
|
||||
}
|
||||
|
||||
// Leave any user-specified attributes on the tag as-is
|
||||
// and add/overwrite the config data needed to boot Blazor
|
||||
InjectBootConfig(bootScript, assemblyName, binFiles);
|
||||
}
|
||||
switch (token.Type)
|
||||
{
|
||||
case HtmlTokenType.StartTag:
|
||||
{
|
||||
// Only do anything special if this is a Blazor boot tag
|
||||
var tag = token.AsTag();
|
||||
if (IsBlazorBootTag(tag))
|
||||
{
|
||||
// First, emit the original source text prior to this special tag, since
|
||||
// we want that to be unchanged
|
||||
resultBuilder.Append(htmlTemplate, currentRangeStartPos, token.Position.Position - currentRangeStartPos - 1);
|
||||
|
||||
// If no blazor-boot script tag was found, we skip it and
|
||||
// leave it up to the user to handle kicking off the boot
|
||||
// Instead of emitting the source text for this special tag, emit a fully-
|
||||
// configured Blazor boot script tag
|
||||
AppendScriptTagWithBootConfig(
|
||||
resultBuilder,
|
||||
assemblyName,
|
||||
binFiles,
|
||||
tag.Attributes);
|
||||
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
dom.ToHtml(writer, new AutoSelectedMarkupFormatter());
|
||||
return writer.ToString();
|
||||
// Set a flag so we know not to emit anything else until the special
|
||||
// tag is closed
|
||||
isInBlazorBootTag = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HtmlTokenType.EndTag:
|
||||
// If this is an end tag corresponding to the Blazor boot script tag, we
|
||||
// can switch back into the mode of emitting the original source text
|
||||
if (isInBlazorBootTag)
|
||||
{
|
||||
isInBlazorBootTag = false;
|
||||
resumeOnNextToken = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case HtmlTokenType.EndOfFile:
|
||||
// Finally, emit any remaining text from the original source file
|
||||
resultBuilder.Append(htmlTemplate, currentRangeStartPos, htmlTemplate.Length - currentRangeStartPos);
|
||||
return resultBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void InjectBootConfig(IElement script, string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||
private static bool IsBlazorBootTag(HtmlTagToken tag)
|
||||
=> string.Equals(tag.Name, "script", StringComparison.Ordinal)
|
||||
&& tag.Attributes.Any(pair =>
|
||||
string.Equals(pair.Key, "type", StringComparison.Ordinal)
|
||||
&& string.Equals(pair.Value, "blazor-boot", StringComparison.Ordinal));
|
||||
|
||||
private static void AppendScriptTagWithBootConfig(
|
||||
StringBuilder resultBuilder,
|
||||
string assemblyName,
|
||||
IEnumerable<IFileInfo> binFiles,
|
||||
List<KeyValuePair<string, string>> attributes)
|
||||
{
|
||||
var assemblyNameWithExtension = $"{assemblyName}.dll";
|
||||
var referenceNames = binFiles
|
||||
|
|
@ -89,9 +132,28 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
|
|||
.Select(file => file.Name);
|
||||
var referencesAttribute = string.Join(",", referenceNames.ToArray());
|
||||
|
||||
script.SetAttribute("src", "/_framework/blazor.js");
|
||||
script.SetAttribute("main", assemblyNameWithExtension);
|
||||
script.SetAttribute("references", referencesAttribute);
|
||||
var attributesDict = attributes.ToDictionary(x => x.Key, x => x.Value);
|
||||
attributesDict.Remove("type");
|
||||
attributesDict["src"] = "/_framework/blazor.js";
|
||||
attributesDict["main"] = assemblyNameWithExtension;
|
||||
attributesDict["references"] = referencesAttribute;
|
||||
|
||||
resultBuilder.Append("<script");
|
||||
foreach (var attributePair in attributesDict)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(attributePair.Value))
|
||||
{
|
||||
resultBuilder.AppendFormat(" {0}=\"{1}\"",
|
||||
attributePair.Key,
|
||||
attributePair.Value); // TODO: HTML attribute encode
|
||||
}
|
||||
else
|
||||
{
|
||||
resultBuilder.AppendFormat(" {0}",
|
||||
attributePair.Key);
|
||||
}
|
||||
}
|
||||
resultBuilder.Append("></script>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ 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>());
|
||||
|
||||
|
|
@ -44,8 +43,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
Assert.False(file.IsDirectory);
|
||||
Assert.Equal("/index.html", file.PhysicalPath);
|
||||
Assert.Equal("index.html", file.Name);
|
||||
Assert.Equal(htmlOutput, ReadString(file));
|
||||
Assert.Equal(htmlOutput.Length, file.Length);
|
||||
Assert.Equal(htmlTemplate, ReadString(file));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -69,7 +67,19 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
public void InjectsScriptTagReferencingAssemblyAndDependencies()
|
||||
{
|
||||
// Arrange
|
||||
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body><script type='blazor-boot'></script></html>";
|
||||
var htmlTemplatePrefix = @"
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello</h1>
|
||||
Some text
|
||||
<script>alert(1)</script>";
|
||||
var htmlTemplateSuffix = @"
|
||||
</body>
|
||||
</html>";
|
||||
var htmlTemplate =
|
||||
$@"{htmlTemplatePrefix}
|
||||
<script type='blazor-boot' custom1 custom2=""value"">some text that should be removed</script>
|
||||
{htmlTemplateSuffix}";
|
||||
var dependencies = new IFileInfo[]
|
||||
{
|
||||
new TestFileInfo("System.Abc.dll"),
|
||||
|
|
@ -80,25 +90,30 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
|
||||
// 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");
|
||||
var fileContents = ReadString(file);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("h1", firstElem.TagName.ToLowerInvariant());
|
||||
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());
|
||||
// Assert: Start and end is not modified (including formatting)
|
||||
Assert.StartsWith(htmlTemplatePrefix, fileContents);
|
||||
Assert.EndsWith(htmlTemplateSuffix, fileContents);
|
||||
|
||||
// Assert: Boot tag is correct
|
||||
var scriptTagText = fileContents.Substring(htmlTemplatePrefix.Length, fileContents.Length - htmlTemplatePrefix.Length - htmlTemplateSuffix.Length);
|
||||
var parsedHtml = new HtmlParser().Parse("<html><body>" + scriptTagText + "</body></html>");
|
||||
var scriptElem = parsedHtml.Body.QuerySelector("script");
|
||||
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"));
|
||||
Assert.Equal(string.Empty, scriptElem.Attributes["custom1"].Value);
|
||||
Assert.Equal("value", scriptElem.Attributes["custom2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingBootScriptTagReferencingAssemblyAndDependencies()
|
||||
public void SuppliesHtmlTemplateUnchangedIfNoBootScriptPresent()
|
||||
{
|
||||
// Arrange
|
||||
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body></html>";
|
||||
var htmlTemplate = "<!DOCTYPE html><html><body><h1 style='color:red'>Hello</h1>Some text<script type='irrelevant'>blah</script></body></html>";
|
||||
var dependencies = new IFileInfo[]
|
||||
{
|
||||
new TestFileInfo("System.Abc.dll"),
|
||||
|
|
@ -109,14 +124,9 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
|
|||
|
||||
// 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.Null(scriptElem);
|
||||
Assert.Equal(htmlTemplate, ReadString(file));
|
||||
}
|
||||
|
||||
private static string ReadString(IFileInfo file)
|
||||
|
|
|
|||
Loading…
Reference in New Issue