diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/App.razor b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/App.razor new file mode 100644 index 000000000000..28a0c6092098 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/App.razor @@ -0,0 +1 @@ +

@typeof(BlazorMultipleApps.FirstClient.Program)

\ No newline at end of file diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/BlazorMultipleApps.FirstClient.csproj b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/BlazorMultipleApps.FirstClient.csproj new file mode 100644 index 000000000000..e3d84a7d4ac3 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/BlazorMultipleApps.FirstClient.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + FirstApp + + + + + + + + + + + + + + <_BlazorBrotliCompressionLevel>NoCompression + + + diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/Program.cs b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/Program.cs new file mode 100644 index 000000000000..11cc7c57ce6d --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/Program.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Text; + +namespace BlazorMultipleApps.FirstClient +{ + public class Program + { + public static void Main(string[] args) + { + GC.KeepAlive(typeof(Newtonsoft.Json.JsonConvert)); + } + } +} diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/wwwroot/css/app.css b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/wwwroot/css/app.css new file mode 100644 index 000000000000..4855bd609d2d --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/wwwroot/css/app.css @@ -0,0 +1 @@ +/* First app.css */ \ No newline at end of file diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/wwwroot/index.html b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/wwwroot/index.html new file mode 100644 index 000000000000..1f450f51fb8a --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.FirstClient/wwwroot/index.html @@ -0,0 +1,14 @@ + + + + + BlazorMultipleApps + + + + + + + + + diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/App.razor b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/App.razor new file mode 100644 index 000000000000..28e849ebde1f --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/App.razor @@ -0,0 +1 @@ +

@typeof(BlazorMultipleApps.SecondClient.Program)

\ No newline at end of file diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/BlazorMultipleApps.SecondClient.csproj b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/BlazorMultipleApps.SecondClient.csproj new file mode 100644 index 000000000000..30356758d74d --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/BlazorMultipleApps.SecondClient.csproj @@ -0,0 +1,22 @@ + + + + net5.0 + SecondApp + + + + + + + + + + + + + <_BlazorBrotliCompressionLevel>NoCompression + + + diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/Program.cs b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/Program.cs new file mode 100644 index 000000000000..c910511317b0 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/Program.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace BlazorMultipleApps.SecondClient +{ + public class Program + { + public static void Main(string[] args) + { + } + } +} diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/wwwroot/css/app.css b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/wwwroot/css/app.css new file mode 100644 index 000000000000..d7cff8ab9dee --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/wwwroot/css/app.css @@ -0,0 +1 @@ +/* Second app.css */ \ No newline at end of file diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/wwwroot/index.html b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/wwwroot/index.html new file mode 100644 index 000000000000..bd7f2bbf7754 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.SecondClient/wwwroot/index.html @@ -0,0 +1,14 @@ + + + + + SecondBlazorApp + + + + + + + + + diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Server/BlazorMultipleApps.Server.csproj b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Server/BlazorMultipleApps.Server.csproj new file mode 100644 index 000000000000..2f62b1cad702 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Server/BlazorMultipleApps.Server.csproj @@ -0,0 +1,19 @@ + + + + net5.0 + + + + + + + + + + + <_BlazorBrotliCompressionLevel>NoCompression + + + diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Server/Program.cs b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Server/Program.cs new file mode 100644 index 000000000000..c571651ff9d6 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Server/Program.cs @@ -0,0 +1,10 @@ +namespace BlazorMultipleApps.Server +{ + public class Program + { + public static void Main(string[] args) + { + + } + } +} diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Shared/BlazorMultipleApps.Shared.csproj b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Shared/BlazorMultipleApps.Shared.csproj new file mode 100644 index 000000000000..d4c395e8cb78 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Shared/BlazorMultipleApps.Shared.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.1 + + + diff --git a/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Shared/WeatherForecast.cs b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Shared/WeatherForecast.cs new file mode 100644 index 000000000000..7c92131eaea7 --- /dev/null +++ b/src/Assets/TestProjects/BlazorMultiApp/BlazorMultipleApps.Shared/WeatherForecast.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BlazorMultipleApps.Shared +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public string Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src/BlazorWasmSdk/Sdk/Sdk.targets b/src/BlazorWasmSdk/Sdk/Sdk.targets index 616c56fb8a0b..4dbd28518446 100644 --- a/src/BlazorWasmSdk/Sdk/Sdk.targets +++ b/src/BlazorWasmSdk/Sdk/Sdk.targets @@ -15,6 +15,10 @@ Copyright (c) .NET Foundation. All rights reserved. <_BlazorWebAssemblyTargetsFile Condition="'$(_BlazorWebAssemblyTargetsFile)' == ''">$(MSBuildThisFileDirectory)..\targets\Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets + + + + diff --git a/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets b/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets index 61cdadedf909..632d6a5b86ed 100644 --- a/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets +++ b/src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets @@ -515,7 +515,7 @@ Copyright (c) .NET Foundation. All rights reserved. <_BlazorPublishBootResource Include="@(ResolvedFileToPublish)" - Condition="$([System.String]::Copy('%(RelativePath)').Replace('\','/').StartsWith('wwwroot/_framework')) AND '%(Extension)' != '.a'" /> + Condition="$([System.String]::Copy('%(RelativePath)').Replace('\','/').StartsWith($(_BlazorFrameworkPublishPath.Replace('\', '/')))) AND '%(Extension)' != '.a'" /> diff --git a/src/RazorSdk/Sdk/Sdk.targets b/src/RazorSdk/Sdk/Sdk.targets index 13a27ba07073..70a55ee2b33a 100644 --- a/src/RazorSdk/Sdk/Sdk.targets +++ b/src/RazorSdk/Sdk/Sdk.targets @@ -14,7 +14,7 @@ Copyright (c) .NET Foundation. All rights reserved. - $(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.targets + $(MSBuildThisFileDirectory)..\build\netstandard2.0\Sdk.Razor.CurrentVersion.targets diff --git a/src/RazorSdk/Tasks/Microsoft.NET.Sdk.Razor.Tasks.csproj b/src/RazorSdk/Tasks/Microsoft.NET.Sdk.Razor.Tasks.csproj index c719e3ff6199..3b2e5e81da50 100644 --- a/src/RazorSdk/Tasks/Microsoft.NET.Sdk.Razor.Tasks.csproj +++ b/src/RazorSdk/Tasks/Microsoft.NET.Sdk.Razor.Tasks.csproj @@ -71,6 +71,9 @@ + + Shared\CommandLine\%(FileName) + Shared\ServerProtocol\%(FileName) diff --git a/src/RazorSdk/Tool/Application.cs b/src/RazorSdk/Tool/Application.cs index f370d824edce..a3c02226f1e2 100644 --- a/src/RazorSdk/Tool/Application.cs +++ b/src/RazorSdk/Tool/Application.cs @@ -88,7 +88,7 @@ public Application( private string GetInformationalVersion() { - var assembly = typeof(Application).GetTypeInfo().Assembly; + var assembly = typeof(Application).Assembly; var attribute = assembly.GetCustomAttribute(); return attribute.InformationalVersion; } diff --git a/src/RazorSdk/Tool/ArgumentEscaper.cs b/src/RazorSdk/Tool/CommandLine/ArgumentEscaper.cs similarity index 98% rename from src/RazorSdk/Tool/ArgumentEscaper.cs rename to src/RazorSdk/Tool/CommandLine/ArgumentEscaper.cs index aa61e6a16298..22f817752934 100644 --- a/src/RazorSdk/Tool/ArgumentEscaper.cs +++ b/src/RazorSdk/Tool/CommandLine/ArgumentEscaper.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; -namespace Microsoft.NET.Sdk.Razor.Tool +namespace Microsoft.NET.Sdk.Razor.Tool.CommandLineUtils { /// /// A utility for escaping arguments for new processes. diff --git a/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs b/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs index 51754942118e..a4fbc2f617d4 100644 --- a/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs +++ b/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.NET.Sdk.Razor.Tool.CommandLineUtils; namespace Microsoft.NET.Sdk.Razor.Tool { @@ -296,8 +297,7 @@ internal static bool TryCreateServerCore(string clientDir, string pipeName, out "-p", pipeName }; - // var processArguments = ArgumentEscaper.EscapeAndConcatenate(argumentList); - var processArguments = argumentList.ToString(); + var processArguments = ArgumentEscaper.EscapeAndConcatenate(argumentList); if (!File.Exists(expectedCompilerPath)) { diff --git a/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPublishTest.cs b/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPublishTest.cs index 744dda365f8b..154c5cd1d74b 100644 --- a/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPublishTest.cs +++ b/src/Tests/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPublishTest.cs @@ -26,6 +26,38 @@ public class WasmPublishIntegrationTest : SdkTest { public WasmPublishIntegrationTest(ITestOutputHelper log) : base(log) { } + [Fact] + public void Publish_MinimalApp_Works() + { + // Arrange + var testAppName = "BlazorWasmMinimal"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + var publishCommand = new PublishCommand(Log, testInstance.TestRoot); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); + + var expectedFiles = new[] + { + "wwwroot/_framework/blazor.boot.json", + "wwwroot/_framework/blazor.webassembly.js", + "wwwroot/_framework/dotnet.wasm", + "wwwroot/_framework/blazorwasm-minimal.dll", + "wwwroot/index.html", + "web.config" + }; + + publishDirectory.Should().HaveFiles(expectedFiles); + + // Verify web.config + var content = File.ReadAllText(Path.Combine(publishDirectory.ToString(), "web.config")); + content.Should().Contain(""); + + VerifyBootManifestHashes(testInstance, Path.Combine(publishDirectory.ToString(), "wwwroot")); + } + [Fact] public void Publish_WithDefaultSettings_Works() { @@ -54,10 +86,6 @@ public void Publish_WithDefaultSettings_Works() "web.config" }; - // Verify web.config - var content = File.ReadAllText(Path.Combine(publishDirectory.ToString(), "web.config")); - content.Should().Contain(""); - publishDirectory.Should().HaveFiles(expectedFiles); var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); @@ -76,6 +104,86 @@ public void Publish_WithDefaultSettings_Works() VerifyTypeGranularTrimming(blazorPublishDirectory); } + [Fact] + public void Publish_WithScopedCss_Works() + { + // Arrange + var testAppName = "BlazorWasmWithLibrary"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + File.WriteAllText(Path.Combine(testInstance.TestRoot, "blazorwasm", "App.razor.css"), "h1 { font-size: 16px; }"); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm")); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); + + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + + var expectedFiles = new[] + { + "wwwroot/_framework/blazor.boot.json", + "wwwroot/_framework/blazor.webassembly.js", + "wwwroot/_framework/dotnet.wasm", + "wwwroot/_framework/blazorwasm.dll", + "wwwroot/_framework/System.Text.Json.dll", + "wwwroot/_content/RazorClassLibrary/wwwroot/exampleJsInterop.js", + "wwwroot/_content/RazorClassLibrary/styles.css", + "wwwroot/index.html", + "wwwroot/js/LinkedScript.js", + "wwwroot/blazorwasm.styles.css", + "wwwroot/css/app.css", + "web.config" + }; + + publishDirectory.Should().HaveFiles(expectedFiles); + + new FileInfo(Path.Combine(blazorPublishDirectory, "css", "app.css")).Should().Contain(".publish"); + + VerifyBootManifestHashes(testInstance, blazorPublishDirectory); + VerifyServiceWorkerFiles(testInstance, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + [Fact] + public void Publish_InRelease_Works() + { + // Arrange + var testAppName = "BlazorWasmWithLibrary"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + File.WriteAllText(Path.Combine(testInstance.TestRoot, "blazorwasm", "App.razor.css"), "h1 { font-size: 16px; }"); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm")); + publishCommand.Execute("/p:Configuration=Release").Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory("net5.0", "Release"); + + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + + var expectedFiles = new[] + { + "wwwroot/_framework/blazor.boot.json", + "wwwroot/_framework/blazor.webassembly.js", + "wwwroot/_framework/dotnet.wasm", + "wwwroot/_framework/blazorwasm.dll", + "wwwroot/_framework/System.Text.Json.dll", + "wwwroot/_content/RazorClassLibrary/wwwroot/exampleJsInterop.js", + "wwwroot/_content/RazorClassLibrary/styles.css", + "wwwroot/index.html", + "wwwroot/js/LinkedScript.js", + "wwwroot/blazorwasm.styles.css", + "wwwroot/css/app.css", + "web.config" + }; + + publishDirectory.Should().HaveFiles(expectedFiles); + + new FileInfo(Path.Combine(blazorPublishDirectory, "css", "app.css")).Should().Contain(".publish"); + } + [Fact] public void Publish_WithExistingWebConfig_Works() { @@ -142,8 +250,8 @@ public void Publish_WithNoBuild_Works() } [Theory] - [InlineData("different-path/")] - [InlineData("/different-path/")] + [InlineData("different-path")] + [InlineData("/different-path")] public void Publish_WithStaticWebBasePathWorks(string basePath) { // Arrange @@ -160,6 +268,7 @@ public void Publish_WithStaticWebBasePathWorks(string basePath) itemGroup.Add(new XElement("StaticWebAssetBasePath", basePath)); project.Root.Add(itemGroup); } + }); var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm")); @@ -204,13 +313,13 @@ public void Publish_WithStaticWebBasePathWorks(string basePath) } [Theory] - [InlineData("different-path")] - [InlineData("/different-path")] + [InlineData("different-path/")] + [InlineData("/different-path/")] public void Publish_Hosted_WithStaticWebBasePathWorks(string basePath) { var testAppName = "BlazorHosted"; var testInstance = _testAssetsManager.CopyTestAsset(testAppName) - .WithSource(); + .WithSource();; testInstance.WithProjectChanges((path, project) => { @@ -221,6 +330,7 @@ public void Publish_Hosted_WithStaticWebBasePathWorks(string basePath) itemGroup.Add(new XElement("StaticWebAssetBasePath", basePath)); project.Root.Add(itemGroup); } + }); var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); @@ -271,24 +381,23 @@ private static void VerifyCompression(TestAsset testAsset, string blazorPublishD } [Fact] - public void Publish_SatelliteAssemblies_AreCopiedToBuildOutput() + public void Publish_WithTrimmingdDisabled_Works() { // Arrange var testAppName = "BlazorWasmWithLibrary"; - var testInstance = _testAssetsManager.CopyTestAsset(testAppName).WithSource(); + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); testInstance.WithProjectChanges((path, project) => { if (path.Contains("blazorwasm")) { var ns = project.Root.Name.Namespace; - var propertyGroup = new XElement(ns + "PropertyGroup"); - propertyGroup.Add(new XElement("DefineConstants", @"$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies")); - var itemGroup = new XElement(ns + "ItemGroup"); - itemGroup.Add(new XElement("ProjectReference", new XAttribute("Include", @"..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"))); - project.Root.Add(propertyGroup); + var itemGroup = new XElement(ns + "PropertyGroup"); + itemGroup.Add(new XElement("PublishTrimmed", false)); project.Root.Add(itemGroup); } + }); var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm")); @@ -299,61 +408,115 @@ public void Publish_SatelliteAssemblies_AreCopiedToBuildOutput() publishDirectory.Should().HaveFiles(new[] { - "wwwroot/_framework/Microsoft.CodeAnalysis.CSharp.dll", - "wwwroot/_framework/fr/Microsoft.CodeAnalysis.CSharp.resources.dll" + "wwwroot/_framework/blazor.boot.json", + "wwwroot/_framework/blazor.webassembly.js", + "wwwroot/_framework/dotnet.wasm", + "wwwroot/_framework/blazorwasm.dll", + "wwwroot/_framework/System.Text.Json.dll" }); - var bootJsonData = new FileInfo(Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json")); - bootJsonData.Should().Contain("\"Microsoft.CodeAnalysis.CSharp.dll\""); - bootJsonData.Should().Contain("\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + // Verify compression works + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_framework/dotnet.wasm.br", + "wwwroot/_framework/System.Text.Json.dll.br" + }); + + // Verify static assets are in the publish directory + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/index.html" + }); + + // Verify referenced static web assets + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_content/RazorClassLibrary/wwwroot/exampleJsInterop.js", + "wwwroot/_content/RazorClassLibrary/styles.css", + }); + + // Verify web.config + publishDirectory.Should().HaveFiles(new[] + { + "web.config" + }); VerifyBootManifestHashes(testInstance, blazorPublishDirectory); + VerifyServiceWorkerFiles(testInstance, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + + // Verify assemblies are not trimmed + var loggingAssemblyPath = Path.Combine(blazorPublishDirectory, "_framework", "Microsoft.Extensions.Logging.Abstractions.dll"); + VerifyAssemblyHasTypes(loggingAssemblyPath, new[] { "Microsoft.Extensions.Logging.Abstractions.NullLogger" }); } [Fact] - public void Publish_HostedApp_WithSatelliteAssemblies() + public void Publish_SatelliteAssemblies_AreCopiedToBuildOutput() { // Arrange - var testAppName = "BlazorHosted"; - var testInstance = _testAssetsManager.CopyTestAsset(testAppName) - .WithSource(); + var testAppName = "BlazorWasmWithLibrary"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName).WithSource(); testInstance.WithProjectChanges((path, project) => { if (path.Contains("blazorwasm")) { var ns = project.Root.Name.Namespace; - // Workaround for https://github.com/mono/linker/issues/1390 var propertyGroup = new XElement(ns + "PropertyGroup"); - - propertyGroup.Add(new XElement("PublishTrimmed", false)); propertyGroup.Add(new XElement("DefineConstants", @"$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies")); var itemGroup = new XElement(ns + "ItemGroup"); itemGroup.Add(new XElement("ProjectReference", new XAttribute("Include", @"..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"))); project.Root.Add(propertyGroup); project.Root.Add(itemGroup); } + }); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm")); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_framework/Microsoft.CodeAnalysis.CSharp.dll", + "wwwroot/_framework/fr/Microsoft.CodeAnalysis.CSharp.resources.dll" }); - var resxfileInProject = Path.Combine(testInstance.TestRoot, "blazorwasm", "Resources.ja.resx.txt"); - File.Move(resxfileInProject, Path.Combine(testInstance.TestRoot, "blazorwasm", "Resource.ja.resx")); + var bootJsonData = new FileInfo(Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json")); + bootJsonData.Should().Contain("\"Microsoft.CodeAnalysis.CSharp.dll\""); + bootJsonData.Should().Contain("\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + + VerifyBootManifestHashes(testInstance, blazorPublishDirectory); + } + + [Fact] + public void Publish_HostedApp_DefaultSettings_Works() + { + // Arrange + var testAppName = "BlazorHosted"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); publishCommand.Execute().Should().Pass(); - var publishOutputDirectory = publishCommand.GetOutputDirectory("net5.0"); + var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); + // Make sure the main project exists + new FileInfo(Path.Combine(publishDirectory.ToString(), "blazorhosted.dll")).Should().Exist(); // Verification for https://github.com/dotnet/aspnetcore/issues/19926. Verify binaries for projects // referenced by the Hosted project appear in the publish directory - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "RazorClassLibrary.dll", "blazorwasm.dll" }); - var blazorPublishDirectory = Path.Combine(publishOutputDirectory.ToString(), "wwwroot"); - publishOutputDirectory.Should().HaveFiles(new[] + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + publishDirectory.Should().HaveFiles(new[] { "wwwroot/_framework/blazor.boot.json", "wwwroot/_framework/blazor.webassembly.js", @@ -363,27 +526,27 @@ public void Publish_HostedApp_WithSatelliteAssemblies() }); // Verify project references appear as static web assets - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "wwwroot/_framework/RazorClassLibrary.dll", "RazorClassLibrary.dll" }); // Verify static assets are in the publish directory - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "wwwroot/index.html" }); // Verify static web assets from referenced projects are copied. - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "wwwroot/_content/RazorClassLibrary/wwwroot/exampleJsInterop.js", "wwwroot/_content/RazorClassLibrary/styles.css", }); // Verify web.config - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "web.config" }); @@ -391,7 +554,7 @@ public void Publish_HostedApp_WithSatelliteAssemblies() VerifyBootManifestHashes(testInstance, blazorPublishDirectory); // Verify compression works - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "wwwroot/_framework/dotnet.wasm.br", "wwwroot/_framework/blazorwasm.dll.br", @@ -399,7 +562,7 @@ public void Publish_HostedApp_WithSatelliteAssemblies() "wwwroot/_framework/System.Text.Json.dll.br" }); - publishOutputDirectory.Should().HaveFiles(new[] + publishDirectory.Should().HaveFiles(new[] { "wwwroot/_framework/dotnet.wasm.gz", "wwwroot/_framework/blazorwasm.dll.gz", @@ -407,8 +570,32 @@ public void Publish_HostedApp_WithSatelliteAssemblies() "wwwroot/_framework/System.Text.Json.dll.gz" }); - // Verify that Blazor bootJSON contains the right contents - var bootJsonPath = Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json"); + VerifyServiceWorkerFiles(testInstance, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + + VerifyTypeGranularTrimming(blazorPublishDirectory); + } + + [Fact] + public void Publish_HostedApp_ProducesBootJsonDataWithExpectedContent() + { + // Arrange + var testAppName = "BlazorHosted"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + var wwwroot = Path.Combine(testInstance.TestRoot, "blazorwasm", "wwwroot"); + File.WriteAllText(Path.Combine(wwwroot, "appsettings.json"), "Default settings"); + File.WriteAllText(Path.Combine(wwwroot, "appsettings.development.json"), "Development settings"); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); + publishCommand.Execute().Should().Pass(); + + var buildOutputDirectory = publishCommand.GetOutputDirectory("net5.0").ToString(); + + var bootJsonPath = Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); var bootJsonData = ReadBootJsonData(bootJsonPath); var runtime = bootJsonData.resources.runtime; @@ -419,12 +606,47 @@ public void Publish_HostedApp_WithSatelliteAssemblies() assemblies.Should().ContainKey("RazorClassLibrary.dll"); assemblies.Should().ContainKey("System.Text.Json.dll"); - VerifyServiceWorkerFiles(testInstance, blazorPublishDirectory, - serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), - serviceWorkerContent: "// This is the production service worker", - assetsManifestPath: "custom-service-worker-assets.js"); + bootJsonData.resources.satelliteResources.Should().BeNull(); - VerifyTypeGranularTrimming(blazorPublishDirectory); + bootJsonData.config.Should().Contain("appsettings.json"); + bootJsonData.config.Should().Contain("appsettings.development.json"); + } + + [Fact] + public void Publish_HostedApp_WithSatelliteAssemblies() + { + // Arrange + var testAppName = "BlazorHosted"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + testInstance.WithProjectChanges((path, project) => + { + if (path.Contains("blazorwasm")) + { + var ns = project.Root.Name.Namespace; + // Workaround for https://github.com/mono/linker/issues/1390 + var propertyGroup = new XElement(ns + "PropertyGroup"); + + propertyGroup.Add(new XElement("PublishTrimmed", false)); + propertyGroup.Add(new XElement("DefineConstants", @"$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies")); + var itemGroup = new XElement(ns + "ItemGroup"); + itemGroup.Add(new XElement("ProjectReference", new XAttribute("Include", @"..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"))); + project.Root.Add(propertyGroup); + project.Root.Add(itemGroup); + } + + }); + + var resxfileInProject = Path.Combine(testInstance.TestRoot, "blazorwasm", "Resources.ja.resx.txt"); + File.Move(resxfileInProject, Path.Combine(testInstance.TestRoot, "blazorwasm", "Resource.ja.resx")); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); + publishCommand.Execute().Should().Pass(); + + var publishOutputDirectory = publishCommand.GetOutputDirectory("net5.0"); + + var bootJsonData = new FileInfo(Path.Combine(publishOutputDirectory.ToString(), "wwwroot", "_framework", "blazor.boot.json")); publishOutputDirectory.Should().HaveFiles(new[] { @@ -434,9 +656,8 @@ public void Publish_HostedApp_WithSatelliteAssemblies() "wwwroot/_framework/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", }); - var bootJsonFile = new FileInfo(Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json")); - bootJsonFile.Should().Contain("\"Microsoft.CodeAnalysis.CSharp.dll\""); - bootJsonFile.Should().Contain("\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + bootJsonData.Should().Contain("\"Microsoft.CodeAnalysis.CSharp.dll\""); + bootJsonData.Should().Contain("\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); } [Fact] @@ -466,7 +687,7 @@ public void Publish_HostedApp_WithoutTrimming_Works() // Publish var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); - publishCommand.Execute("/p:BuildDependencies=false").Should().Pass(); + publishCommand.Execute("/p:BuildDependencies=false /bl").Should().Pass(); var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); // Make sure the main project exists @@ -595,30 +816,97 @@ public void Publish_HostedApp_WithNoBuild_Works() } [Fact] - public void Publish_HostedAppWithScopedCssAndSatelliteAssemblies_VisualStudio() + public void Publish_HostedApp_VisualStudio() { // Simulates publishing the same way VS does by setting BuildProjectReferences=false. + // Arrange var testAppName = "BlazorHosted"; var testInstance = _testAssetsManager.CopyTestAsset(testAppName) .WithSource(); - testInstance.WithProjectChanges((path, project) => + // VS builds projects individually and then a publish with BuildDependencies=false, but building the main project is a close enough approximation for this test. + var buildCommand = new BuildCommand(testInstance, "blazorwasm"); + buildCommand.Execute("/p:BuildInsideVisualStudio=true").Should().Pass(); + + // Publish + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); + publishCommand.Execute("/p:BuildProjectReferences=false /p:BuildInsideVisualStudio=true").Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); + // Make sure the main project exists + new FileInfo(Path.Combine(publishDirectory.ToString(), "blazorhosted.dll")).Should().Exist(); + + // Verification for https://github.com/dotnet/aspnetcore/issues/19926. Verify binaries for projects + // referenced by the Hosted project appear in the publish directory + publishDirectory.Should().HaveFiles(new[] { - if (path.Contains("blazorwasm")) - { - var ns = project.Root.Name.Namespace; - var propertyGroup = new XElement(ns + "PropertyGroup"); - propertyGroup.Add(new XElement("DefineConstants", @"$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies")); - var itemGroup = new XElement(ns + "ItemGroup"); - itemGroup.Add(new XElement("ProjectReference", new XAttribute("Include", @"..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"))); - project.Root.Add(propertyGroup); - project.Root.Add(itemGroup); - } + "RazorClassLibrary.dll", + "blazorwasm.dll" }); - var resxfileInProject = Path.Combine(testInstance.TestRoot, "blazorwasm", "Resources.ja.resx.txt"); - File.Move(resxfileInProject, Path.Combine(testInstance.TestRoot, "blazorwasm", "Resource.ja.resx")); + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_framework/blazor.boot.json", + "wwwroot/_framework/blazor.webassembly.js", + "wwwroot/_framework/dotnet.wasm", + "wwwroot/_framework/blazorwasm.dll", + "wwwroot/_framework/System.Text.Json.dll" + }); + + // Verify project references appear as static web assets + // Also verify project references to the server project appear in the publish output + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_framework/RazorClassLibrary.dll", + "RazorClassLibrary.dll" + }); + + // Verify static assets are in the publish directory + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/index.html" + }); + + // Verify static web assets from referenced projects are copied. + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_content/RazorClassLibrary/wwwroot/exampleJsInterop.js", + "wwwroot/_content/RazorClassLibrary/styles.css", + }); + + // Verify web.config + publishDirectory.Should().HaveFiles(new[] + { + "web.config" + }); + VerifyBootManifestHashes(testInstance, Path.Combine(publishDirectory.ToString(), "wwwroot")); + + // Verify compression works + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_framework/dotnet.wasm.br", + "wwwroot/_framework/blazorwasm.dll.br", + "wwwroot/_framework/RazorClassLibrary.dll.br", + "wwwroot/_framework/System.Text.Json.dll.br" + }); + + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + + VerifyBootManifestHashes(testInstance, blazorPublishDirectory); + VerifyServiceWorkerFiles(testInstance, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + [Fact] + public void Publish_HostedAppWithScopedCss_VisualStudio() + { + // Simulates publishing the same way VS does by setting BuildProjectReferences=false. + var testAppName = "BlazorHosted"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); File.WriteAllText(Path.Combine(testInstance.TestRoot, "blazorwasm", "App.razor.css"), "h1 { font-size: 16px; }"); // VS builds projects individually and then a publish with BuildDependencies=false, but building the main project is a close enough approximation for this test. @@ -630,24 +918,9 @@ public void Publish_HostedAppWithScopedCssAndSatelliteAssemblies_VisualStudio() publishCommand.Execute("/p:BuildProjectReferences=false /p:BuildInsideVisualStudio=true").Should().Pass(); var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); - var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); - // Make sure the main project exists new FileInfo(Path.Combine(publishDirectory.ToString(), "blazorhosted.dll")).Should().Exist(); - // Verification for satelitte assemblies - publishDirectory.Should().HaveFiles(new[] - { - "wwwroot/_framework/blazor.boot.json", - "wwwroot/_framework/ja/blazorwasm.resources.dll", - "wwwroot/_framework/fr/Microsoft.CodeAnalysis.CSharp.resources.dll" - }); - - var bootJsonData = new FileInfo(Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json")); - bootJsonData.Should().Contain("\"es-ES\\/classlibrarywithsatelliteassemblies.resources.dll\""); - bootJsonData.Should().Contain("\"ja\\/blazorwasm.resources.dll\""); - bootJsonData.Should().Contain("\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); - // Verification for https://github.com/dotnet/aspnetcore/issues/19926. Verify binaries for projects // referenced by the Hosted project appear in the publish directory publishDirectory.Should().HaveFiles(new[] @@ -709,6 +982,8 @@ public void Publish_HostedAppWithScopedCssAndSatelliteAssemblies_VisualStudio() "wwwroot/_framework/System.Text.Json.dll.br" }); + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + VerifyBootManifestHashes(testInstance, blazorPublishDirectory); VerifyServiceWorkerFiles(testInstance, blazorPublishDirectory, serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), @@ -716,6 +991,55 @@ public void Publish_HostedAppWithScopedCssAndSatelliteAssemblies_VisualStudio() assetsManifestPath: "custom-service-worker-assets.js"); } + // 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] + public void Publish_HostedApp_VisualStudio_WithSatelliteAssemblies() + { + var testAppName = "BlazorWasmWithLibrary"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName).WithSource(); + + testInstance.WithProjectChanges((path, project) => + { + if (path.Contains("blazorwasm")) + { + var ns = project.Root.Name.Namespace; + var propertyGroup = new XElement(ns + "PropertyGroup"); + propertyGroup.Add(new XElement("DefineConstants", @"$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies")); + var itemGroup = new XElement(ns + "ItemGroup"); + itemGroup.Add(new XElement("ProjectReference", new XAttribute("Include", @"..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"))); + project.Root.Add(propertyGroup); + project.Root.Add(itemGroup); + } + }); + + var resxfileInProject = Path.Combine(testInstance.TestRoot, "blazorwasm", "Resources.ja.resx.txt"); + File.Move(resxfileInProject, Path.Combine(testInstance.TestRoot, "blazorwasm", "Resource.ja.resx")); + + var buildCommand = new BuildCommand(testInstance, "blazorwasm"); + buildCommand.Execute().Should().Pass(); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm")); + publishCommand.Execute("/p:BuildProjectReferences=false").Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory("net5.0"); + var blazorPublishDirectory = Path.Combine(publishDirectory.ToString(), "wwwroot"); + + publishDirectory.Should().HaveFiles(new[] + { + "wwwroot/_framework/blazor.boot.json", + "wwwroot/_framework/ja/blazorwasm.resources.dll", + "wwwroot/_framework/fr/Microsoft.CodeAnalysis.CSharp.resources.dll" + }); + + var bootJsonData = new FileInfo(Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json")); + bootJsonData.Should().Contain("\"es-ES\\/classlibrarywithsatelliteassemblies.resources.dll\""); + bootJsonData.Should().Contain("\"ja\\/blazorwasm.resources.dll\""); + bootJsonData.Should().Contain("\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + + VerifyBootManifestHashes(testInstance, blazorPublishDirectory); + } + [Fact] public void Publish_HostedApp_WithRidSpecifiedInCLI_Works() { @@ -730,6 +1054,19 @@ public void Publish_HostedApp_WithRidSpecifiedInCLI_Works() AssertRIDPublishOuput(publishCommand, testInstance); } + [Fact] + public void Publish_HostedApp_WithRid_Works() + { + // Arrange + var testAppName = "BlazorHostedRID"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName).WithSource(); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorhosted")); + publishCommand.Execute().Should().Pass(); + + AssertRIDPublishOuput(publishCommand, testInstance); + } + private static void AssertRIDPublishOuput(PublishCommand command, TestAsset testInstance) { var publishDirectory = command.GetOutputDirectory("net5.0", "Debug", "linux-x64"); @@ -837,6 +1174,7 @@ public void Publish_WithInvariantGlobalizationEnabled_DoesNotCopyGlobalizationDa runtime.Should().NotContainKey("icudt.dat"); runtime.Should().NotContainKey("icudt_EFIGS.dat"); + new FileInfo(Path.Combine(publishOutputDirectory, "wwwroot", "_framework", "dotnet.wasm")).Should().Exist(); new FileInfo(Path.Combine(publishOutputDirectory, "wwwroot", "_framework", "icudt.dat")).Should().NotExist(); new FileInfo(Path.Combine(publishOutputDirectory, "wwwroot", "_framework", "icudt_CJK.dat")).Should().NotExist(); @@ -844,6 +1182,64 @@ public void Publish_WithInvariantGlobalizationEnabled_DoesNotCopyGlobalizationDa new FileInfo(Path.Combine(publishOutputDirectory, "wwwroot", "_framework", "icudt_no_CJK.dat")).Should().NotExist(); } + [Fact] + public void Publish_HostingMultipleBlazorWebApps_Works() + { + // Regression test for https://github.com/dotnet/aspnetcore/issues/29264 + // Arrange + var testAppName = "BlazorMultiApp"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + var publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "BlazorMultipleApps.Server")); + publishCommand.Execute().Should().Pass(); + + var publishOutputDirectory = publishCommand.GetOutputDirectory("net5.0").ToString(); + + new FileInfo(Path.Combine(publishOutputDirectory, "BlazorMultipleApps.Server.dll")).Should().Exist(); + new FileInfo(Path.Combine(publishOutputDirectory, "BlazorMultipleApps.FirstClient.dll")).Should().Exist(); + new FileInfo(Path.Combine(publishOutputDirectory, "BlazorMultipleApps.SecondClient.dll")).Should().Exist(); + + var firstAppPublishDirectory = Path.Combine(publishOutputDirectory, "wwwroot", "FirstApp"); + + var firstCss = Path.Combine(firstAppPublishDirectory, "css", "app.css"); + new FileInfo(firstCss).Should().Exist(); + new FileInfo(firstCss).Should().Exist("/* First app.css */"); + + var firstBootJsonPath = Path.Combine(firstAppPublishDirectory, "_framework", "blazor.boot.json"); + var firstBootJson = ReadBootJsonData(firstBootJsonPath); + + // Do a sanity check that the boot json has files. + firstBootJson.resources.assembly.Keys.Should().Contain("System.Text.Json.dll"); + + VerifyBootManifestHashes(testInstance, firstAppPublishDirectory); + + // Verify compression works + new FileInfo(Path.Combine(firstAppPublishDirectory, "_framework", "dotnet.wasm.br")).Should().Exist(); + new FileInfo(Path.Combine(firstAppPublishDirectory, "_framework", "BlazorMultipleApps.FirstClient.dll.br")).Should().Exist(); + new FileInfo(Path.Combine(firstAppPublishDirectory, "_framework", "Newtonsoft.Json.dll.br")).Should().Exist(); + + var secondAppPublishDirectory = Path.Combine(publishOutputDirectory, "wwwroot", "SecondApp"); + + var secondCss = Path.Combine(secondAppPublishDirectory, "css", "app.css"); + new FileInfo(secondCss).Should().Exist(); + new FileInfo(secondCss).Should().Exist("/* Second app.css */"); + + var secondBootJsonPath = Path.Combine(secondAppPublishDirectory, "_framework", "blazor.boot.json"); + var secondBootJson = ReadBootJsonData(secondBootJsonPath); + + // Do a sanity check that the boot json has files. + secondBootJson.resources.assembly.Keys.Should().Contain("System.Private.CoreLib.dll"); + + VerifyBootManifestHashes(testInstance, secondAppPublishDirectory); + + // Verify compression works + new FileInfo(Path.Combine(secondAppPublishDirectory, "_framework", "dotnet.wasm.br")).Should().Exist(); + new FileInfo(Path.Combine(secondAppPublishDirectory, "_framework", "BlazorMultipleApps.SecondClient.dll.br")).Should().Exist(); + new FileInfo(Path.Combine(secondAppPublishDirectory, "_framework", "System.Private.CoreLib.dll.br")).Should().Exist(); + new FileInfo(Path.Combine(secondAppPublishDirectory, "_framework", "Newtonsoft.Json.dll.br")).Should().NotExist(); + } + private static void VerifyBootManifestHashes(TestAsset testAsset, string blazorPublishDirectory) { var bootManifestResolvedPath = Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json");