diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs
index 4b1c904906..88e8393930 100644
--- a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs
+++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs
@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.Blazor.RenderTree;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -20,6 +21,13 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
{
private const string builderVarName = "builder";
+ // Per the HTML spec, the following elements are inherently self-closing
+ // For example,
is the same as
(and therefore it cannot contain descendants)
+ private static HashSet htmlVoidElementsLookup
+ = new HashSet(
+ new[] { "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" },
+ StringComparer.OrdinalIgnoreCase);
+
public override void BeginWriterScope(CodeRenderingContext context, string writer)
{
throw new System.NotImplementedException(nameof(BeginWriterScope));
@@ -119,26 +127,37 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
switch (nextToken.Type)
{
case HtmlTokenType.Character:
- // Text node
- context.CodeWriter
- .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}")
- .WriteStringLiteral(nextToken.Data)
- .WriteEndMethodInvocation();
- break;
+ {
+ // Text node
+ context.CodeWriter
+ .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}")
+ .WriteStringLiteral(nextToken.Data)
+ .WriteEndMethodInvocation();
+ break;
+ }
case HtmlTokenType.StartTag:
- var nextTag = nextToken.AsTag();
- context.CodeWriter
- .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.OpenElement)}")
- .WriteStringLiteral(nextTag.Data)
- .WriteEndMethodInvocation();
- break;
-
case HtmlTokenType.EndTag:
- context.CodeWriter
- .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.CloseElement)}")
- .WriteEndMethodInvocation();
- break;
+ {
+ var nextTag = nextToken.AsTag();
+ if (nextToken.Type == HtmlTokenType.StartTag)
+ {
+ context.CodeWriter
+ .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.OpenElement)}")
+ .WriteStringLiteral(nextTag.Data)
+ .WriteEndMethodInvocation();
+ }
+
+ if (nextToken.Type == HtmlTokenType.EndTag
+ || nextTag.IsSelfClosing
+ || htmlVoidElementsLookup.Contains(nextTag.Data))
+ {
+ context.CodeWriter
+ .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.CloseElement)}")
+ .WriteEndMethodInvocation();
+ }
+ break;
+ }
default:
throw new InvalidCastException($"Unsupported token type: {nextToken.Type.ToString()}");
diff --git a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs
index 8bfba05999..ac7f730c4e 100644
--- a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs
+++ b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs
@@ -128,12 +128,35 @@ namespace Microsoft.Blazor.Build.Test
var component = CompileToComponent("Hello");
// Assert
- var nodes = GetRenderTree(component).Where(NotWhitespace);
- Assert.Collection(nodes,
+ Assert.Collection(GetRenderTree(component),
node => AssertNode.Element(node, "myelem", 1),
node => AssertNode.Text(node, "Hello"));
}
+ [Fact]
+ public void CanRenderSelfClosingElements()
+ {
+ // Arrange/Act
+ var component = CompileToComponent("Some text so elem isn't at position 0 ");
+
+ // Assert
+ Assert.Collection(GetRenderTree(component),
+ node => AssertNode.Text(node, "Some text so elem isn't at position 0 "),
+ node => AssertNode.Element(node, "myelem", 1));
+ }
+
+ [Fact]
+ public void CanRenderVoidHtmlElements()
+ {
+ // Arrange/Act
+ var component = CompileToComponent("Some text so elem isn't at position 0
");
+
+ // Assert
+ Assert.Collection(GetRenderTree(component),
+ node => AssertNode.Text(node, "Some text so elem isn't at position 0 "),
+ node => AssertNode.Element(node, "img", 1));
+ }
+
private static bool NotWhitespace(RenderTreeNode node)
=> node.NodeType != RenderTreeNodeType.Text
|| !string.IsNullOrWhiteSpace(node.TextContent);