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:
Pranav K 2020-07-31 13:40:52 -07:00 committed by GitHub
parent e6b9a3d295
commit f495fcb151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 5 deletions

View File

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

View File

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

View File

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