Skip to content

Commit 90b697f

Browse files
Razor compiler emit CSS scope attributes (#23703)
1 parent fae3dd1 commit 90b697f

File tree

29 files changed

+940
-44
lines changed

29 files changed

+940
-44
lines changed

src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ public sealed partial class RenderTreeBuilder : System.IDisposable
407407
{
408408
public RenderTreeBuilder() { }
409409
public void AddAttribute(int sequence, in Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame frame) { }
410+
public void AddAttribute(int sequence, string name) { }
410411
public void AddAttribute(int sequence, string name, Microsoft.AspNetCore.Components.EventCallback value) { }
411412
public void AddAttribute(int sequence, string name, bool value) { }
412413
public void AddAttribute(int sequence, string name, System.MulticastDelegate? value) { }

src/Components/Components/src/Rendering/RenderTreeBuilder.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,29 @@ public void AddContent(int sequence, MarkupString markupContent)
157157
public void AddContent(int sequence, object? textContent)
158158
=> AddContent(sequence, textContent?.ToString());
159159

160+
/// <summary>
161+
/// <para>
162+
/// Appends a frame representing a bool-valued attribute with value 'true'.
163+
/// </para>
164+
/// <para>
165+
/// The attribute is associated with the most recently added element.
166+
/// </para>
167+
/// </summary>
168+
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
169+
/// <param name="name">The name of the attribute.</param>
170+
public void AddAttribute(int sequence, string name)
171+
{
172+
ProfilingStart();
173+
174+
if (_lastNonAttributeFrameType != RenderTreeFrameType.Element)
175+
{
176+
throw new InvalidOperationException($"Valueless attributes may only be added immediately after frames of type {RenderTreeFrameType.Element}");
177+
}
178+
179+
Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue));
180+
ProfilingEnd();
181+
}
182+
160183
/// <summary>
161184
/// <para>
162185
/// Appends a frame representing a bool-valued attribute.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Razor.Language.Intermediate;
6+
7+
namespace Microsoft.AspNetCore.Razor.Language.Components
8+
{
9+
internal class ComponentCssScopePass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
10+
{
11+
// Runs after components/bind, since it's preferable for the auto-generated attribute to appear later
12+
// in the DOM than developer-written ones
13+
public override int Order => 110;
14+
15+
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
16+
{
17+
if (!IsComponentDocument(documentNode))
18+
{
19+
return;
20+
}
21+
22+
var cssScope = codeDocument.GetCssScope();
23+
if (string.IsNullOrEmpty(cssScope))
24+
{
25+
return;
26+
}
27+
28+
var nodes = documentNode.FindDescendantNodes<MarkupElementIntermediateNode>();
29+
for (var i = 0; i < nodes.Count; i++)
30+
{
31+
ProcessElement(nodes[i], cssScope);
32+
}
33+
}
34+
35+
private void ProcessElement(MarkupElementIntermediateNode node, string cssScope)
36+
{
37+
// Add a minimized attribute whose name is simply the CSS scope
38+
node.Children.Add(new HtmlAttributeIntermediateNode
39+
{
40+
AttributeName = cssScope,
41+
Prefix = cssScope,
42+
Suffix = string.Empty,
43+
});
44+
}
45+
}
46+
}

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,7 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, string
316316
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(ComponentsApi.RenderTreeBuilder.AddAttribute)}")
317317
.Write("-1")
318318
.WriteParameterSeparator()
319-
.WriteStringLiteral(key)
320-
.WriteParameterSeparator();
319+
.WriteStringLiteral(key);
321320
}
322321

323322
protected override void BeginWriteAttribute(CodeRenderingContext context, IntermediateNode expression)
@@ -341,8 +340,6 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, Interm
341340
{
342341
context.CodeWriter.Write(tokens[i].Content);
343342
}
344-
345-
context.CodeWriter.WriteParameterSeparator();
346343
}
347344

348345
public override void WriteComponent(CodeRenderingContext context, ComponentIntermediateNode node)
@@ -731,6 +728,7 @@ public override void WriteComponentChildContent(CodeRenderingContext context, Co
731728
// OR
732729
// __builder.AddAttribute(1, "ChildContent", (RenderFragment<Person>)((person) => (__builder73) => { ... }));
733730
BeginWriteAttribute(context, node.AttributeName);
731+
context.CodeWriter.WriteParameterSeparator();
734732
context.CodeWriter.Write($"({node.TypeName})(");
735733

736734
WriteComponentChildContentInnards(context, node);

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ public override void WriteComponentChildContent(CodeRenderingContext context, Co
673673
// OR
674674
// _builder.AddAttribute(1, "ChildContent", (RenderFragment<Person>)((person) => (__builder73) => { ... }));
675675
BeginWriteAttribute(context, node.AttributeName);
676+
context.CodeWriter.WriteParameterSeparator();
676677
context.CodeWriter.Write($"({node.TypeName})(");
677678

678679
WriteComponentChildContentInnards(context, node);
@@ -851,14 +852,22 @@ protected override void WriteReferenceCaptureInnards(CodeRenderingContext contex
851852
private void WriteAttribute(CodeRenderingContext context, string key, IList<IntermediateToken> value)
852853
{
853854
BeginWriteAttribute(context, key);
854-
WriteAttributeValue(context, value);
855+
if (value.Count > 0)
856+
{
857+
context.CodeWriter.WriteParameterSeparator();
858+
WriteAttributeValue(context, value);
859+
}
855860
context.CodeWriter.WriteEndMethodInvocation();
856861
}
857862

858863
private void WriteAttribute(CodeRenderingContext context, IntermediateNode nameExpression, IList<IntermediateToken> value)
859864
{
860865
BeginWriteAttribute(context, nameExpression);
861-
WriteAttributeValue(context, value);
866+
if (value.Count > 0)
867+
{
868+
context.CodeWriter.WriteParameterSeparator();
869+
WriteAttributeValue(context, value);
870+
}
862871
context.CodeWriter.WriteEndMethodInvocation();
863872
}
864873

@@ -868,8 +877,7 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, string
868877
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{ComponentsApi.RenderTreeBuilder.AddAttribute}")
869878
.Write((_sourceSequence++).ToString())
870879
.WriteParameterSeparator()
871-
.WriteStringLiteral(key)
872-
.WriteParameterSeparator();
880+
.WriteStringLiteral(key);
873881
}
874882

875883
protected override void BeginWriteAttribute(CodeRenderingContext context, IntermediateNode nameExpression)
@@ -883,8 +891,6 @@ protected override void BeginWriteAttribute(CodeRenderingContext context, Interm
883891
{
884892
WriteCSharpToken(context, tokens[i]);
885893
}
886-
887-
context.CodeWriter.WriteParameterSeparator();
888894
}
889895

890896
private static string GetHtmlContent(HtmlContentIntermediateNode node)
@@ -988,8 +994,7 @@ private static void WriteAttributeValue(CodeRenderingContext context, IList<Inte
988994
}
989995
else
990996
{
991-
// Minimized attributes always map to 'true'
992-
writer.Write("true");
997+
throw new InvalidOperationException("Found attribute whose value is neither HTML nor CSharp");
993998
}
994999
}
9951000

src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorProjectEngine.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ protected RazorCodeDocument CreateCodeDocumentCore(
8585
}
8686

8787
var importSourceDocuments = GetImportSourceDocuments(importItems);
88-
return CreateCodeDocumentCore(sourceDocument, projectItem.FileKind, importSourceDocuments, tagHelpers: null, configureParser, configureCodeGeneration);
88+
return CreateCodeDocumentCore(sourceDocument, projectItem.FileKind, importSourceDocuments, tagHelpers: null, configureParser, configureCodeGeneration, cssScope: projectItem.CssScope);
8989
}
9090

9191
protected internal RazorCodeDocument CreateCodeDocumentCore(
@@ -94,7 +94,8 @@ protected internal RazorCodeDocument CreateCodeDocumentCore(
9494
IReadOnlyList<RazorSourceDocument> importSourceDocuments = null,
9595
IReadOnlyList<TagHelperDescriptor> tagHelpers = null,
9696
Action<RazorParserOptionsBuilder> configureParser = null,
97-
Action<RazorCodeGenerationOptionsBuilder> configureCodeGeneration = null)
97+
Action<RazorCodeGenerationOptionsBuilder> configureCodeGeneration = null,
98+
string cssScope = null)
9899
{
99100
if (sourceDocument == null)
100101
{
@@ -122,6 +123,11 @@ protected internal RazorCodeDocument CreateCodeDocumentCore(
122123
codeDocument.SetFileKind(fileKind);
123124
}
124125

126+
if (cssScope != null)
127+
{
128+
codeDocument.SetCssScope(cssScope);
129+
}
130+
125131
return codeDocument;
126132
}
127133

src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorProjectFileSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
4040
var relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator
4141
var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/');
4242

43-
return new DefaultRazorProjectItem(basePath, filePath, relativePhysicalPath, fileKind: null, file);
43+
return new DefaultRazorProjectItem(basePath, filePath, relativePhysicalPath, fileKind: null, file, cssScope: null);
4444
});
4545
}
4646

@@ -58,7 +58,7 @@ public override RazorProjectItem GetItem(string path, string fileKind)
5858
var relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator
5959
var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/');
6060

61-
return new DefaultRazorProjectItem("/", filePath, relativePhysicalPath, fileKind, new FileInfo(absolutePath));
61+
return new DefaultRazorProjectItem("/", filePath, relativePhysicalPath, fileKind, new FileInfo(absolutePath), cssScope: null);
6262
}
6363

6464
[Obsolete("Use GetItem(string path, string fileKind) instead.")]

src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorProjectItem.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ internal class DefaultRazorProjectItem : RazorProjectItem
1717
/// <param name="filePath">The path.</param>
1818
/// <param name="fileKind">The file kind. If null, the document kind will be inferred from the file extension.</param>
1919
/// <param name="file">The <see cref="FileInfo"/>.</param>
20-
public DefaultRazorProjectItem(string basePath, string filePath, string relativePhysicalPath, string fileKind, FileInfo file)
20+
/// <param name="cssScope">A scope identifier that will be used on elements in the generated class, or null.</param>
21+
public DefaultRazorProjectItem(string basePath, string filePath, string relativePhysicalPath, string fileKind, FileInfo file, string cssScope)
2122
{
2223
BasePath = basePath;
2324
FilePath = filePath;
2425
RelativePhysicalPath = relativePhysicalPath;
2526
_fileKind = fileKind;
2627
File = file;
28+
CssScope = cssScope;
2729
}
2830

2931
public FileInfo File { get; }
@@ -40,6 +42,8 @@ public DefaultRazorProjectItem(string basePath, string filePath, string relative
4042

4143
public override string FileKind => _fileKind ?? base.FileKind;
4244

45+
public override string CssScope { get; }
46+
4347
public override Stream Read() => new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
4448
}
4549
}

src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static class RazorCodeDocumentExtensions
1414
{
1515
private static readonly char[] PathSeparators = new char[] { '/', '\\' };
1616
private static readonly char[] NamespaceSeparators = new char[] { '.' };
17+
private static object CssScopeKey = new object();
1718

1819
public static TagHelperDocumentContext GetTagHelperContext(this RazorCodeDocument document)
1920
{
@@ -216,6 +217,26 @@ public static void SetFileKind(this RazorCodeDocument document, string fileKind)
216217
document.Items[typeof(FileKinds)] = fileKind;
217218
}
218219

220+
public static string GetCssScope(this RazorCodeDocument document)
221+
{
222+
if (document == null)
223+
{
224+
throw new ArgumentNullException(nameof(document));
225+
}
226+
227+
return (string)document.Items[CssScopeKey];
228+
}
229+
230+
public static void SetCssScope(this RazorCodeDocument document, string cssScope)
231+
{
232+
if (document == null)
233+
{
234+
throw new ArgumentNullException(nameof(document));
235+
}
236+
237+
document.Items[CssScopeKey] = cssScope;
238+
}
239+
219240
// In general documents will have a relative path (relative to the project root).
220241
// We can only really compute a nice namespace when we know a relative path.
221242
//

src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngine.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ private static void AddComponentFeatures(RazorProjectEngineBuilder builder, Razo
238238
builder.Features.Add(new ComponentReferenceCaptureLoweringPass());
239239
builder.Features.Add(new ComponentSplatLoweringPass());
240240
builder.Features.Add(new ComponentBindLoweringPass());
241+
builder.Features.Add(new ComponentCssScopePass());
241242
builder.Features.Add(new ComponentTemplateDiagnosticPass());
242243
builder.Features.Add(new ComponentGenericTypePass());
243244
builder.Features.Add(new ComponentChildContentDiagnosticPass());

0 commit comments

Comments
 (0)