Avoid doing unncecessary work when generating component declaration files. (#24445)
The output of the declaration file for Razor components are unaffected by all inputs other than the input .razor file.
Consequently we can avoid regenerating these files if the output is newer than the input. This is the same heuristic we apply to Blazor WebAsssembly's
compression artifacts.
This PR combines these two improvements for a ~90ms (10%) improvement in the inner loop.
```
17 ms GenerateBlazorWebAssemblyBootJson 1 calls
22 ms Copy 8 calls
39 ms ProcessFrameworkReferences 1 calls
40 ms RazorTagHelper 1 calls
51 ms ResolveAssemblyReference 1 calls
70 ms GetFileHash 1 calls
80 ms RazorGenerate 2 calls
111 ms Csc 2 calls
Time Elapsed 00:00:00.95
```
```
17 ms GenerateBlazorWebAssemblyBootJson 1 calls
21 ms Copy 8 calls
37 ms ProcessFrameworkReferences 1 calls
51 ms ResolveAssemblyReference 1 calls
70 ms Csc 1 calls
72 ms GetFileHash 1 calls
79 ms RazorGenerate 2 calls
Time Elapsed 00:00:00.86
```
In after: Csc calls reduced to one, RazorTagHelper call removed.
This commit is contained in:
parent
e6b9a3d295
commit
f495fcb151
|
|
@ -201,6 +201,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
if (GenerateDeclaration.HasValue())
|
||||
{
|
||||
b.Features.Add(new SetSuppressPrimaryMethodBodyOptionFeature());
|
||||
b.Features.Add(new SuppressChecksumOptionsFeature());
|
||||
}
|
||||
|
||||
if (RootNamespace.HasValue())
|
||||
|
|
@ -227,6 +228,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
});
|
||||
|
||||
var results = GenerateCode(engine, sourceItems);
|
||||
var isGeneratingDeclaration = GenerateDeclaration.HasValue();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
|
|
@ -255,6 +257,18 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
{
|
||||
// Only output the file if we generated it without errors.
|
||||
var outputFilePath = result.InputItem.OutputPath;
|
||||
var generatedCode = result.CSharpDocument.GeneratedCode;
|
||||
if (isGeneratingDeclaration)
|
||||
{
|
||||
// When emiting declarations, only write if it the contents are different.
|
||||
// This allows build incrementalism to kick in when the declaration remains unchanged between builds.
|
||||
if (File.Exists(outputFilePath) &&
|
||||
string.Equals(File.ReadAllText(outputFilePath), generatedCode, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllText(outputFilePath, result.CSharpDocument.GeneratedCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,9 +174,10 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
|
||||
[Fact]
|
||||
[InitializeTestProject("MvcWithComponents")]
|
||||
public async Task BuildComponents_RegeneratesComponentDefinition_WhenFilesChange()
|
||||
public async Task BuildComponents_DoesNotRegenerateComponentDefinition_WhenDefinitionIsUnchanged()
|
||||
{
|
||||
// Act - 1
|
||||
var updatedContent = "Some content";
|
||||
var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
|
||||
|
||||
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
|
||||
|
|
@ -204,7 +205,56 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
var definitionThumbprint = GetThumbPrint(tagHelperOutputCache);
|
||||
|
||||
// Act - 2
|
||||
ReplaceContent("Different things", "Views", "Shared", "NavMenu.razor");
|
||||
ReplaceContent(updatedContent, "Views", "Shared", "NavMenu.razor");
|
||||
result = await DotnetMSBuild("Build");
|
||||
|
||||
// Assert - 2
|
||||
Assert.FileExists(result, generatedDefinitionFile);
|
||||
// Definition file remains unchanged.
|
||||
Assert.Equal(generatedDefinitionThumbprint, GetThumbPrint(generatedDefinitionFile));
|
||||
Assert.FileExists(result, generatedFile);
|
||||
// Generated file should change and include the new content.
|
||||
Assert.NotEqual(generatedFileThumbprint, GetThumbPrint(generatedFile));
|
||||
Assert.FileContains(result, generatedFile, updatedContent);
|
||||
|
||||
// TagHelper cache should remain unchanged.
|
||||
Assert.Equal(definitionThumbprint, GetThumbPrint(tagHelperOutputCache));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[InitializeTestProject("MvcWithComponents")]
|
||||
public async Task BuildComponents_RegeneratesComponentDefinition_WhenFilesChange()
|
||||
{
|
||||
// Act - 1
|
||||
var updatedContent = "@code { [Parameter] public string AParameter { get; set; } }";
|
||||
var tagHelperOutputCache = Path.Combine(IntermediateOutputPath, "MvcWithComponents.TagHelpers.output.cache");
|
||||
|
||||
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
|
||||
var generatedDefinitionFile = Path.Combine(RazorComponentIntermediateOutputPath, "Views", "Shared", "NavMenu.razor.g.cs");
|
||||
|
||||
// Assert - 1
|
||||
var result = await DotnetMSBuild("Build");
|
||||
|
||||
Assert.BuildPassed(result);
|
||||
var outputFile = Path.Combine(OutputPath, "MvcWithComponents.dll");
|
||||
Assert.FileExists(result, OutputPath, "MvcWithComponents.dll");
|
||||
var outputAssemblyThumbprint = GetThumbPrint(outputFile);
|
||||
|
||||
Assert.FileExists(result, generatedDefinitionFile);
|
||||
var generatedDefinitionThumbprint = GetThumbPrint(generatedDefinitionFile);
|
||||
Assert.FileExists(result, generatedFile);
|
||||
var generatedFileThumbprint = GetThumbPrint(generatedFile);
|
||||
|
||||
Assert.FileExists(result, tagHelperOutputCache);
|
||||
Assert.FileContains(
|
||||
result,
|
||||
tagHelperOutputCache,
|
||||
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
|
||||
|
||||
var definitionThumbprint = GetThumbPrint(tagHelperOutputCache);
|
||||
|
||||
// Act - 2
|
||||
ReplaceContent(updatedContent, "Views", "Shared", "NavMenu.razor");
|
||||
result = await DotnetMSBuild("Build");
|
||||
|
||||
// Assert - 2
|
||||
|
|
@ -222,8 +272,12 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|||
tagHelperOutputCache,
|
||||
@"""Name"":""MvcWithComponents.Views.Shared.NavMenu""");
|
||||
|
||||
// TODO:
|
||||
Assert.Equal(definitionThumbprint, GetThumbPrint(tagHelperOutputCache));
|
||||
Assert.FileContains(
|
||||
result,
|
||||
tagHelperOutputCache,
|
||||
"AParameter");
|
||||
|
||||
Assert.NotEqual(definitionThumbprint, GetThumbPrint(tagHelperOutputCache));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
<!-- Used for tracking inputs to component generation -->
|
||||
<_RazorComponentInputHash></_RazorComponentInputHash>
|
||||
<_RazorComponentInputCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).RazorComponent.input.cache</_RazorComponentInputCacheFile>
|
||||
<_RazorComponentDeclarationOutputCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).RazorComponent.output.cache</_RazorComponentDeclarationOutputCacheFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -85,7 +86,7 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
Name="RazorGenerateComponentDeclaration"
|
||||
DependsOnTargets="$(RazorGenerateComponentDeclarationDependsOn)"
|
||||
Inputs="$(MSBuildAllProjects);@(RazorComponentWithTargetPath);$(_RazorComponentInputCacheFile)"
|
||||
Outputs="@(_RazorComponentDeclaration)"
|
||||
Outputs="$(_RazorComponentDeclarationOutputCacheFile)"
|
||||
Condition="'@(RazorComponentWithTargetPath->Count())'!='0'">
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -120,8 +121,13 @@ Copyright (c) .NET Foundation. All rights reserved.
|
|||
TagHelperManifest="$(_RazorComponentDeclarationManifest)"
|
||||
GenerateDeclaration="true" />
|
||||
|
||||
<Touch
|
||||
Files="$(_RazorComponentDeclarationOutputCacheFile)"
|
||||
AlwaysCreate="true" />
|
||||
|
||||
<ItemGroup>
|
||||
<FileWrites Include="@(_RazorComponentDeclaration)" />
|
||||
<FileWrites Include="$(_RazorComponentDeclarationOutputCacheFile)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue