diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Assert.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Assert.cs
index 6583ce89d0..27c19d0748 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Assert.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Assert.cs
@@ -319,6 +319,22 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
return filePath;
}
+ public static string DirectoryExists(MSBuildResult result, params string[] paths)
+ {
+ if (result == null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ var filePath = Path.Combine(result.Project.DirectoryPath, Path.Combine(paths));
+ if (!Directory.Exists(filePath))
+ {
+ throw new DirectoryMissingException(result, filePath);
+ }
+
+ return filePath;
+ }
+
public static void FileCountEquals(MSBuildResult result, int expected, string directoryPath, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories)
{
if (result == null)
@@ -820,6 +836,19 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
protected override string Heading => $"File: '{FilePath}' was not found.";
}
+ private class DirectoryMissingException : MSBuildXunitException
+ {
+ public DirectoryMissingException(MSBuildResult result, string directoryPath)
+ : base(result)
+ {
+ DirectoryPath = directoryPath;
+ }
+
+ public string DirectoryPath { get; }
+
+ protected override string Heading => $"Directory: '{DirectoryPath}' was not found.";
+ }
+
private class FileCountException : MSBuildXunitException
{
public FileCountException(MSBuildResult result, int expected, string directoryPath, string searchPattern, string[] files)
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs
index 301d688e40..f91dd03863 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs
@@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
public class WasmBuildIncrementalismTest
{
[Fact]
- public async Task Build_WithLinker_IsIncremental()
+ public async Task Build_IsIncremental()
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary" });
@@ -40,6 +40,37 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
}
+ [Fact]
+ public async Task Build_GzipCompression_IsIncremental()
+ {
+ // Arrange
+ using var project = ProjectDirectory.Create("blazorwasm", additionalProjects: new[] { "razorclasslibrary" });
+ var result = await MSBuildProcessManager.DotnetMSBuild(project);
+
+ Assert.BuildPassed(result);
+
+ var gzipCompressionDirectory = Path.Combine(project.IntermediateOutputDirectory, "build-gz");
+
+ Assert.DirectoryExists(result, gzipCompressionDirectory);
+
+ // Act
+ var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, gzipCompressionDirectory);
+
+ // Assert
+ for (var i = 0; i < 3; i++)
+ {
+ result = await MSBuildProcessManager.DotnetMSBuild(project);
+ Assert.BuildPassed(result);
+
+ var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, gzipCompressionDirectory);
+ Assert.Equal(thumbPrint.Count, newThumbPrint.Count);
+ for (var j = 0; j < thumbPrint.Count; j++)
+ {
+ Assert.Equal(thumbPrint[j], newThumbPrint[j]);
+ }
+ }
+ }
+
[Fact]
public async Task Build_SatelliteAssembliesFileIsPreserved()
{
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs
index 8788d9ce17..12fbbd0380 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs
@@ -28,10 +28,14 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "dotnet.wasm");
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "dotnet.wasm.gz");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", DotNetJsFileName);
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazorwasm.dll");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "RazorClassLibrary.dll");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.Text.Json.dll.gz"); // Verify dependencies are part of the output.
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.dll");
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.dll.gz");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazorwasm.pdb");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "RazorClassLibrary.pdb");
@@ -55,10 +59,14 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "dotnet.wasm");
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "dotnet.wasm.gz");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", DotNetJsFileName);
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazorwasm.dll");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "RazorClassLibrary.dll");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.Text.Json.dll"); // Verify dependencies are part of the output.
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.Text.Json.dll.gz");
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.dll");
+ Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "System.dll.gz");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazorwasm.pdb");
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "RazorClassLibrary.pdb");
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs
index e308d78274..2814308b10 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs
@@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var buildOutputDirectory = project.BuildOutputDirectory;
// Act
- var compressedFilesFolder = Path.Combine("..", "blazorwasm", project.IntermediateOutputDirectory, "brotli");
+ var compressedFilesFolder = Path.Combine("..", "blazorwasm", project.IntermediateOutputDirectory, "compress");
var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder);
// Assert
@@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var buildOutputDirectory = project.BuildOutputDirectory;
// Act
- var compressedFilesFolder = Path.Combine("..", "blazorwasm", project.IntermediateOutputDirectory, "brotli");
+ var compressedFilesFolder = Path.Combine("..", "blazorwasm", project.IntermediateOutputDirectory, "compress");
var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder);
// Assert
@@ -157,6 +157,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
var extension = Path.GetExtension(file);
if (extension != ".br" && extension != ".gz")
{
+ Assert.FileExists(result, file + ".gz");
Assert.FileExists(result, file + ".br");
}
}
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs
index fdcb60b864..adbe0c18f4 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs
@@ -208,7 +208,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
// Verify compression works
Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.br");
- Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br"); //
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br"); //
// Verify static assets are in the publish directory
Assert.FileExists(result, blazorPublishDirectory, "index.html");
@@ -311,6 +311,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.gz");
+
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
@@ -352,6 +357,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
[Fact]
+ [QuarantinedTest]
public async Task Publish_HostedApp_WithSatelliteAssemblies()
{
// Arrange
@@ -444,6 +450,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.gz");
+
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
@@ -539,6 +550,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
// Regression test to verify satellite assemblies from the blazor app are copied to the published app's wwwroot output directory as
// part of publishing in VS
[Fact]
+ [QuarantinedTest]
public async Task Publish_HostedApp_VisualStudio_WithSatelliteAssemblies()
{
// Simulates publishing the same way VS does by setting BuildProjectReferences=false.
@@ -643,6 +655,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.br");
Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.br");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "dotnet.wasm.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazorwasm.dll.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "RazorClassLibrary.dll.gz");
+ Assert.FileExists(result, blazorPublishDirectory, "_framework", "System.Text.Json.dll.gz");
+
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
serviceWorkerContent: "// This is the production service worker",
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/BrotliCompress.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/BrotliCompress.cs
index 9019f0520f..1ac1c343d7 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/src/BrotliCompress.cs
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/BrotliCompress.cs
@@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
var input = FilesToCompress[i];
var inputFullPath = input.GetMetadata("FullPath");
var relativePath = input.GetMetadata("RelativePath");
- var outputRelativePath = Path.Combine(OutputDirectory, CalculateTargetPath(relativePath));
+ var outputRelativePath = Path.Combine(OutputDirectory, CalculateTargetPath(relativePath, ".br"));
var outputItem = new TaskItem(outputRelativePath);
input.CopyMetadataTo(outputItem);
@@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
return builder.ToString();
}
- private static string CalculateTargetPath(string relativePath)
+ internal static string CalculateTargetPath(string relativePath, string extension)
{
// RelativePath can be long and if used as-is to write the output, might result in long path issues on Windows.
// Instead we'll calculate a fixed length path by hashing the input file name. This uses SHA1 similar to the Hash task in MSBuild
@@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Razor.Tasks
builder.Append(InvalidPathChars.Contains(c) ? '+' : c);
}
- builder.Append(".br");
+ builder.Append(extension);
return builder.ToString();
}
}
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/GZipCompress.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/GZipCompress.cs
new file mode 100644
index 0000000000..6649ee5d9c
--- /dev/null
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/GZipCompress.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.AspNetCore.Razor.Tasks
+{
+ public class GZipCompress : Task
+ {
+ [Required]
+ public ITaskItem[] FilesToCompress { get; set; }
+
+ [Output]
+ public ITaskItem[] CompressedFiles { get; set; }
+
+ [Required]
+ public string OutputDirectory { get; set; }
+
+ public override bool Execute()
+ {
+ CompressedFiles = new ITaskItem[FilesToCompress.Length];
+
+ Directory.CreateDirectory(OutputDirectory);
+
+ System.Threading.Tasks.Parallel.For(0, FilesToCompress.Length, i =>
+ {
+ var file = FilesToCompress[i];
+ var inputPath = file.ItemSpec;
+ var relativePath = file.GetMetadata("RelativePath");
+ var outputRelativePath = Path.Combine(
+ OutputDirectory,
+ BrotliCompress.CalculateTargetPath(relativePath, ".gz"));
+
+ var outputItem = new TaskItem(outputRelativePath);
+ outputItem.SetMetadata("RelativePath", relativePath + ".gz");
+ CompressedFiles[i] = outputItem;
+
+ if (File.Exists(outputRelativePath) && File.GetLastWriteTimeUtc(inputPath) < File.GetLastWriteTimeUtc(outputRelativePath))
+ {
+ // Incrementalism. If input source doesn't exist or it exists and is not newer than the expected output, do nothing.
+ Log.LogMessage(MessageImportance.Low, $"Skipping '{inputPath}' because '{outputRelativePath}' is newer than '{inputPath}'.");
+ return;
+ }
+
+ try
+ {
+ using var sourceStream = File.OpenRead(inputPath);
+ using var fileStream = File.Create(outputRelativePath);
+ using var stream = new GZipStream(fileStream, CompressionLevel.Optimal);
+
+ sourceStream.CopyTo(stream);
+ }
+ catch (Exception e)
+ {
+ Log.LogErrorFromException(e);
+ return;
+ }
+ });
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Components.Wasm.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Components.Wasm.targets
index 9c06ac8210..37994fdc6b 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Components.Wasm.targets
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Components.Wasm.targets
@@ -15,6 +15,7 @@ Copyright (c) .NET Foundation. All rights reserved.
+
@@ -151,6 +152,30 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+ <_BlazorBuildGZipCompressDirectory>$(IntermediateOutputPath)build-gz\
+
+
+
+
+ <_GzipFileToCompressForBuild
+ Include="@(ReferenceCopyLocalPaths)"
+ RelativePath="$(_BlazorOutputPath)%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)"
+ Condition="'%(Extension)' == '.dll' or '%(ReferenceCopyLocalPaths.AssetType)' == 'native'" />
+
+
+
+
+
+
+
+
<_BlazorWriteSatelliteAssembly Include="@(_BlazorOutputWithTargetPath->HasMetadata('Culture'))" />
@@ -238,6 +263,15 @@ Copyright (c) .NET Foundation. All rights reserved.
Never
+ <_BlazorWebAssemblyStaticWebAsset Include="@(_BlazorBuildGZipCompressedFile)">
+ $(PackageId)
+
+ $([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))
+ $(StaticWebAssetBasePath)
+ $([System.String]::Copy('%(_BlazorBuildGZipCompressedFile.RelativePath)').Replace('\','/').Substring(8))
+ Never
+
+
<_ExternalStaticWebAsset Include="@(_BlazorWebAssemblyStaticWebAsset)" SourceType="Generated" />
@@ -305,6 +339,20 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
@@ -411,12 +459,12 @@ Copyright (c) .NET Foundation. All rights reserved.
- <_CompressedFileOutputPath>$(IntermediateOutputPath)brotli\
+ <_CompressedFileOutputPath>$(IntermediateOutputPath)compress\
<_BlazorWebAssemblyBrotliIncremental>true
- <_BrotliFileToCompress
+ <_FileToCompress
Include="@(ResolvedFileToPublish)"
Condition="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('\','/').StartsWith('wwwroot/'))" />
@@ -427,7 +475,7 @@ Copyright (c) .NET Foundation. All rights reserved.
@@ -436,8 +484,17 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
+
+