diff --git a/AspNetCore.sln b/AspNetCore.sln
index ca9dfcf281..50068277d8 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1427,6 +1427,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Extensions.Tests", "src\Components\Web.Extensions\test\Microsoft.AspNetCore.Components.Web.Extensions.Tests.csproj", "{157605CB-5170-4C1A-980F-4BAE42DB60DE}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{FED4267E-E5E4-49C5-98DB-8B3F203596EE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.NET.Sdk.BlazorWebAssembly", "src\Components\WebAssembly\Sdk\src\Microsoft.NET.Sdk.BlazorWebAssembly.csproj", "{6B2734BF-C61D-4889-ABBF-456A4075D59B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.NET.Sdk.BlazorWebAssembly.Tests", "src\Components\WebAssembly\Sdk\test\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj", "{83371889-9A3E-4D16-AE77-EB4F83BC6374}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests", "src\Components\WebAssembly\Sdk\integrationtests\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj", "{525EBCB4-A870-470B-BC90-845306C337D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.NET.Sdk.BlazorWebAssembly.Tools", "src\Components\WebAssembly\Sdk\tools\Microsoft.NET.Sdk.BlazorWebAssembly.Tools.csproj", "{175E5CD8-92D4-46BB-882E-3A930D3302D4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -6729,6 +6739,54 @@ Global
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x64.Build.0 = Release|Any CPU
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x86.ActiveCfg = Release|Any CPU
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x86.Build.0 = Release|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|x64.Build.0 = Debug|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|x86.Build.0 = Debug|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Release|x64.ActiveCfg = Release|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Release|x64.Build.0 = Release|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Release|x86.ActiveCfg = Release|Any CPU
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B}.Release|x86.Build.0 = Release|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Debug|x64.Build.0 = Debug|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Debug|x86.Build.0 = Debug|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Release|x64.ActiveCfg = Release|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Release|x64.Build.0 = Release|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Release|x86.ActiveCfg = Release|Any CPU
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374}.Release|x86.Build.0 = Release|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Debug|x64.Build.0 = Debug|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Debug|x86.Build.0 = Debug|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Release|x64.ActiveCfg = Release|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Release|x64.Build.0 = Release|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Release|x86.ActiveCfg = Release|Any CPU
+ {525EBCB4-A870-470B-BC90-845306C337D1}.Release|x86.Build.0 = Release|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Debug|x64.Build.0 = Debug|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Debug|x86.Build.0 = Debug|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Release|x64.ActiveCfg = Release|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Release|x64.Build.0 = Release|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Release|x86.ActiveCfg = Release|Any CPU
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -7444,6 +7502,11 @@ Global
{F71FE795-9923-461B-9809-BB1821A276D0} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF}
{8294A74F-7DAA-4B69-BC56-7634D93C9693} = {F71FE795-9923-461B-9809-BB1821A276D0}
{157605CB-5170-4C1A-980F-4BAE42DB60DE} = {F71FE795-9923-461B-9809-BB1821A276D0}
+ {FED4267E-E5E4-49C5-98DB-8B3F203596EE} = {562D5067-8CD8-4F19-BCBB-873204932C61}
+ {6B2734BF-C61D-4889-ABBF-456A4075D59B} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
+ {83371889-9A3E-4D16-AE77-EB4F83BC6374} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
+ {525EBCB4-A870-470B-BC90-845306C337D1} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
+ {175E5CD8-92D4-46BB-882E-3A930D3302D4} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
diff --git a/eng/Build.props b/eng/Build.props
index 56b4917250..3740546dd6 100644
--- a/eng/Build.props
+++ b/eng/Build.props
@@ -172,6 +172,7 @@
@(ProjectToBuild);
@(ProjectToExclude);
$(RepoRoot)src\Razor\test\testassets\**\*.*proj;
+ $(RepoRoot)src\Components\WebAssembly\Sdk\testassets\**\*.*proj;
$(RepoRoot)**\node_modules\**\*;
$(RepoRoot)**\bin\**\*;
$(RepoRoot)**\obj\**\*;"
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index a0e89782ca..6dbfca4646 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -145,6 +145,7 @@
+
diff --git a/src/Components/Components.slnf b/src/Components/Components.slnf
index c202f7c1c6..637ec1a384 100644
--- a/src/Components/Components.slnf
+++ b/src/Components/Components.slnf
@@ -110,6 +110,9 @@
"src\\Components\\WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj",
"src\\Components\\WebAssembly\\testassets\\Wasm.Authentication.Client\\Wasm.Authentication.Client.csproj",
"src\\Components\\WebAssembly\\testassets\\Wasm.Authentication.Shared\\Wasm.Authentication.Shared.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\src\\Microsoft.NET.Sdk.BlazorWebAssembly.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\test\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\integrationtests\\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj",
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj"
]
}
diff --git a/src/Components/ComponentsNoDeps.slnf b/src/Components/ComponentsNoDeps.slnf
index 6dbf031fd3..dd08970d02 100644
--- a/src/Components/ComponentsNoDeps.slnf
+++ b/src/Components/ComponentsNoDeps.slnf
@@ -31,6 +31,10 @@
"src\\Components\\WebAssembly\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"src\\Components\\WebAssembly\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"src\\Components\\WebAssembly\\testassets\\StandaloneApp\\StandaloneApp.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\src\\Microsoft.NET.Sdk.BlazorWebAssembly.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\test\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\tools\\Microsoft.NET.Sdk.BlazorWebAssembly.Tools.csproj",
+ "src\\Components\\WebAssembly\\Sdk\\integrationtests\\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj",
"src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
"src\\Components\\Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
"src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
diff --git a/src/Components/Directory.Build.props b/src/Components/Directory.Build.props
index 1704a1b070..b1ea764edd 100644
--- a/src/Components/Directory.Build.props
+++ b/src/Components/Directory.Build.props
@@ -1,14 +1,6 @@
-
-
-
-
-
-
-
-
true
diff --git a/src/Components/Directory.Build.targets b/src/Components/Directory.Build.targets
index 69204d61a6..f3143253af 100644
--- a/src/Components/Directory.Build.targets
+++ b/src/Components/Directory.Build.targets
@@ -3,7 +3,7 @@
$(RepoRoot)src\Components\Web.JS\dist\$(Configuration)\blazor.webassembly.js
$(BlazorWebAssemblyJSPath).map
- <_BlazorDevServerPath>$(RepoRoot)src/Components/WebAssembly/DevServer/src/bin/$(Configuration)/$(DefaultNetCoreTargetFramework)/blazor-devserver.dll
+ <_BlazorDevServerPath>$(ArtifactsDir)/bin/Microsoft.AspNetCore.Components.WebAssembly.DevServer/$(Configuration)/$(DefaultNetCoreTargetFramework)/blazor-devserver.dll
dotnet
exec "$(_BlazorDevServerPath)" serve --applicationpath "$(TargetPath)" $(AdditionalRunArguments)
diff --git a/src/Components/WebAssembly/Sdk/integrationtests/Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj b/src/Components/WebAssembly/Sdk/integrationtests/Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj
new file mode 100644
index 0000000000..4255ce7c73
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/integrationtests/Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj
@@ -0,0 +1,78 @@
+
+
+
+
+ $(DefaultNetCoreTargetFramework)
+ true
+
+
+ false
+ $(MSBuildProjectDirectory)\..\testassets\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ true
+
+
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_DesktopMSBuildPath Condition="'$(OS)' == 'Windows_NT' and Exists('$(_VSInstallDir)\MSBuild\Current\Bin\msbuild.exe')">$(_VSInstallDir)\MSBuild\Current\Bin\msbuild.exe
+ <_DesktopMSBuildPath Condition="'$(OS)' == 'Windows_NT' and Exists('$(_VSInstallDir)\MSBuild\15.0\Bin\msbuild.exe')">$(_VSInstallDir)\MSBuild\15.0\Bin\msbuild.exe
+
+
+
+
+
+
+ <_Parameter1>DesktopMSBuildPath
+ <_Parameter2>$(_DesktopMSBuildPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/ServiceWorkerAssert.cs b/src/Components/WebAssembly/Sdk/integrationtests/ServiceWorkerAssert.cs
similarity index 98%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/ServiceWorkerAssert.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/ServiceWorkerAssert.cs
index 7585b5b76c..8597d10480 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/ServiceWorkerAssert.cs
+++ b/src/Components/WebAssembly/Sdk/integrationtests/ServiceWorkerAssert.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
-using Microsoft.AspNetCore.Razor.Tasks;
+using Microsoft.NET.Sdk.BlazorWebAssembly;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmBuildIncrementalismTest.cs
similarity index 99%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/WasmBuildIncrementalismTest.cs
index f91dd03863..218567fcc5 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIncrementalismTest.cs
+++ b/src/Components/WebAssembly/Sdk/integrationtests/WasmBuildIncrementalismTest.cs
@@ -4,7 +4,7 @@
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Tasks;
+using Microsoft.NET.Sdk.BlazorWebAssembly;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmBuildIntegrationTest.cs
similarity index 99%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/WasmBuildIntegrationTest.cs
index 12fbbd0380..c521c6be7e 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildIntegrationTest.cs
+++ b/src/Components/WebAssembly/Sdk/integrationtests/WasmBuildIntegrationTest.cs
@@ -4,7 +4,7 @@
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Tasks;
+using Microsoft.NET.Sdk.BlazorWebAssembly;
using Microsoft.AspNetCore.Testing;
using Xunit;
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmBuildLazyLoadTest.cs
similarity index 99%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/WasmBuildLazyLoadTest.cs
index e8f1a02d40..424caeda2d 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmBuildLazyLoadTest.cs
+++ b/src/Components/WebAssembly/Sdk/integrationtests/WasmBuildLazyLoadTest.cs
@@ -4,7 +4,7 @@
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Tasks;
+using Microsoft.NET.Sdk.BlazorWebAssembly;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmCompressionTests.cs
similarity index 100%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmCompressionTests.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/WasmCompressionTests.cs
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs
similarity index 98%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs
index 45c878ea5a..1108d0bff9 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPublishIntegrationTest.cs
+++ b/src/Components/WebAssembly/Sdk/integrationtests/WasmPublishIntegrationTest.cs
@@ -5,7 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Text.Json;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Tasks;
+using Microsoft.NET.Sdk.BlazorWebAssembly;
using Microsoft.AspNetCore.Testing;
using Xunit;
using static Microsoft.AspNetCore.Razor.Design.IntegrationTests.ServiceWorkerAssert;
@@ -608,6 +608,17 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
assetsManifestPath: "custom-service-worker-assets.js");
}
+ [Fact]
+ public async Task Publish_HostedApp_WithRidSpecifiedInCLI_Works()
+ {
+ // Arrange
+ using var project = ProjectDirectory.Create("blazorhosted-rid", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
+ project.RuntimeIdentifier = "linux-x64";
+ var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:RuntimeIdentifier=linux-x64");
+
+ AssertRIDPublishOuput(project, result);
+ }
+
[Fact]
public async Task Publish_HostedApp_WithRid_Works()
{
@@ -616,6 +627,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
project.RuntimeIdentifier = "linux-x64";
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
+ AssertRIDPublishOuput(project, result);
+ }
+
+ private static void AssertRIDPublishOuput(ProjectDirectory project, MSBuildResult result)
+ {
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPwaManifestTests.cs b/src/Components/WebAssembly/Sdk/integrationtests/WasmPwaManifestTests.cs
similarity index 100%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Wasm/WasmPwaManifestTests.cs
rename to src/Components/WebAssembly/Sdk/integrationtests/WasmPwaManifestTests.cs
diff --git a/src/Components/WebAssembly/Sdk/integrationtests/xunit.runner.json b/src/Components/WebAssembly/Sdk/integrationtests/xunit.runner.json
new file mode 100644
index 0000000000..d00e4ae907
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/integrationtests/xunit.runner.json
@@ -0,0 +1,5 @@
+{
+ "methodDisplay": "method",
+ "shadowCopy": false,
+ "maxParallelThreads": -1
+}
\ No newline at end of file
diff --git a/src/Components/WebAssembly/Sdk/src/AssetsManifestFile.cs b/src/Components/WebAssembly/Sdk/src/AssetsManifestFile.cs
new file mode 100644
index 0000000000..872b17aae2
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/AssetsManifestFile.cs
@@ -0,0 +1,33 @@
+// 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.
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+#pragma warning disable IDE1006 // Naming Styles
+ public class AssetsManifestFile
+ {
+ ///
+ /// Gets or sets a version string.
+ ///
+ public string version { get; set; }
+
+ ///
+ /// Gets or sets the assets. Keys are URLs; values are base-64-formatted SHA256 content hashes.
+ ///
+ public AssetsManifestFileEntry[] assets { get; set; }
+ }
+
+ public class AssetsManifestFileEntry
+ {
+ ///
+ /// Gets or sets the asset URL. Normally this will be relative to the application's base href.
+ ///
+ public string url { get; set; }
+
+ ///
+ /// Gets or sets the file content hash. This should be the base-64-formatted SHA256 value.
+ ///
+ public string hash { get; set; }
+ }
+#pragma warning restore IDE1006 // Naming Styles
+}
diff --git a/src/Components/WebAssembly/Sdk/src/BlazorReadSatelliteAssemblyFile.cs b/src/Components/WebAssembly/Sdk/src/BlazorReadSatelliteAssemblyFile.cs
new file mode 100644
index 0000000000..f6bf0c88f8
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/BlazorReadSatelliteAssemblyFile.cs
@@ -0,0 +1,38 @@
+// 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.Linq;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public class BlazorReadSatelliteAssemblyFile : Task
+ {
+ [Output]
+ public ITaskItem[] SatelliteAssembly { get; set; }
+
+ [Required]
+ public ITaskItem ReadFile { get; set; }
+
+ public override bool Execute()
+ {
+ var document = XDocument.Load(ReadFile.ItemSpec);
+ SatelliteAssembly = document.Root
+ .Elements()
+ .Select(e =>
+ {
+ //
+
+ var taskItem = new TaskItem(e.Attribute("Name").Value);
+ taskItem.SetMetadata("Culture", e.Attribute("Culture").Value);
+ taskItem.SetMetadata("DestinationSubDirectory", e.Attribute("DestinationSubDirectory").Value);
+
+ return taskItem;
+ }).ToArray();
+
+ return true;
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/src/BlazorWriteSatelliteAssemblyFile.cs b/src/Components/WebAssembly/Sdk/src/BlazorWriteSatelliteAssemblyFile.cs
new file mode 100644
index 0000000000..92fd8d33e8
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/BlazorWriteSatelliteAssemblyFile.cs
@@ -0,0 +1,53 @@
+// 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.IO;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public class BlazorWriteSatelliteAssemblyFile : Task
+ {
+ [Required]
+ public ITaskItem[] SatelliteAssembly { get; set; }
+
+ [Required]
+ public ITaskItem WriteFile { get; set; }
+
+ public override bool Execute()
+ {
+ using var fileStream = File.Create(WriteFile.ItemSpec);
+ WriteSatelliteAssemblyFile(fileStream);
+ return true;
+ }
+
+ internal void WriteSatelliteAssemblyFile(Stream stream)
+ {
+ var root = new XElement("SatelliteAssembly");
+
+ foreach (var item in SatelliteAssembly)
+ {
+ //
+
+ root.Add(new XElement("Assembly",
+ new XAttribute("Name", item.ItemSpec),
+ new XAttribute("Culture", item.GetMetadata("Culture")),
+ new XAttribute("DestinationSubDirectory", item.GetMetadata("DestinationSubDirectory"))));
+ }
+
+ var xmlWriterSettings = new XmlWriterSettings
+ {
+ Indent = true,
+ OmitXmlDeclaration = true
+ };
+
+ using var writer = XmlWriter.Create(stream, xmlWriterSettings);
+ var xDocument = new XDocument(root);
+
+ xDocument.Save(writer);
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/src/BootJsonData.cs b/src/Components/WebAssembly/Sdk/src/BootJsonData.cs
new file mode 100644
index 0000000000..b3a2dbd388
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/BootJsonData.cs
@@ -0,0 +1,85 @@
+// 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.Collections.Generic;
+using System.Runtime.Serialization;
+using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+#pragma warning disable IDE1006 // Naming Styles
+ ///
+ /// Defines the structure of a Blazor boot JSON file
+ ///
+ public class BootJsonData
+ {
+ ///
+ /// Gets the name of the assembly with the application entry point
+ ///
+ public string entryAssembly { get; set; }
+
+ ///
+ /// Gets the set of resources needed to boot the application. This includes the transitive
+ /// closure of .NET assemblies (including the entrypoint assembly), the dotnet.wasm file,
+ /// and any PDBs to be loaded.
+ ///
+ /// Within , dictionary keys are resource names,
+ /// and values are SHA-256 hashes formatted in prefixed base-64 style (e.g., 'sha256-abcdefg...')
+ /// as used for subresource integrity checking.
+ ///
+ public ResourcesData resources { get; set; } = new ResourcesData();
+
+ ///
+ /// Gets a value that determines whether to enable caching of the
+ /// inside a CacheStorage instance within the browser.
+ ///
+ public bool cacheBootResources { get; set; }
+
+ ///
+ /// Gets a value that determines if this is a debug build.
+ ///
+ public bool debugBuild { get; set; }
+
+ ///
+ /// Gets a value that determines if the linker is enabled.
+ ///
+ public bool linkerEnabled { get; set; }
+
+ ///
+ /// Config files for the application
+ ///
+ public List config { get; set; }
+ }
+
+ public class ResourcesData
+ {
+ ///
+ /// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc.
+ ///
+ public ResourceHashesByNameDictionary runtime { get; set; } = new ResourceHashesByNameDictionary();
+
+ ///
+ /// "assembly" (.dll) resources
+ ///
+ public ResourceHashesByNameDictionary assembly { get; set; } = new ResourceHashesByNameDictionary();
+
+ ///
+ /// "debug" (.pdb) resources
+ ///
+ [DataMember(EmitDefaultValue = false)]
+ public ResourceHashesByNameDictionary pdb { get; set; }
+
+ ///
+ /// localization (.satellite resx) resources
+ ///
+ [DataMember(EmitDefaultValue = false)]
+ public Dictionary satelliteResources { get; set; }
+
+ ///
+ /// Assembly (.dll) resources that are loaded lazily during runtime
+ ///
+ [DataMember(EmitDefaultValue = false)]
+ public ResourceHashesByNameDictionary lazyAssembly { get; set; }
+ }
+#pragma warning restore IDE1006 // Naming Styles
+}
diff --git a/src/Components/WebAssembly/Sdk/src/BrotliCompress.cs b/src/Components/WebAssembly/Sdk/src/BrotliCompress.cs
new file mode 100644
index 0000000000..89b3004a5e
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/BrotliCompress.cs
@@ -0,0 +1,125 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public class BrotliCompress : ToolTask
+ {
+ private static readonly char[] InvalidPathChars = Path.GetInvalidFileNameChars();
+ private string _dotnetPath;
+
+ [Required]
+ public ITaskItem[] FilesToCompress { get; set; }
+
+ [Output]
+ public ITaskItem[] CompressedFiles { get; set; }
+
+ [Required]
+ public string OutputDirectory { get; set; }
+
+ public string CompressionLevel { get; set; }
+
+ public bool SkipIfOutputIsNewer { get; set; }
+
+ [Required]
+ public string ToolAssembly { get; set; }
+
+ protected override string ToolName => Path.GetDirectoryName(DotNetPath);
+
+ private string DotNetPath
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(_dotnetPath))
+ {
+ return _dotnetPath;
+ }
+
+ _dotnetPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH");
+ if (string.IsNullOrEmpty(_dotnetPath))
+ {
+ throw new InvalidOperationException("DOTNET_HOST_PATH is not set");
+ }
+
+ return _dotnetPath;
+ }
+ }
+
+ protected override string GenerateCommandLineCommands() => ToolAssembly;
+
+ protected override string GenerateResponseFileCommands()
+ {
+ var builder = new StringBuilder();
+
+
+ builder.AppendLine("brotli");
+
+ if (!string.IsNullOrEmpty(CompressionLevel))
+ {
+ builder.AppendLine("-c");
+ builder.AppendLine(CompressionLevel);
+ }
+
+ CompressedFiles = new ITaskItem[FilesToCompress.Length];
+
+ for (var i = 0; i < FilesToCompress.Length; i++)
+ {
+ var input = FilesToCompress[i];
+ var inputFullPath = input.GetMetadata("FullPath");
+ var relativePath = input.GetMetadata("RelativePath");
+ var outputRelativePath = Path.Combine(OutputDirectory, CalculateTargetPath(inputFullPath, ".br"));
+ var outputFullPath = Path.GetFullPath(outputRelativePath);
+
+ var outputItem = new TaskItem(outputRelativePath);
+ outputItem.SetMetadata("RelativePath", relativePath + ".br");
+ CompressedFiles[i] = outputItem;
+
+ if (SkipIfOutputIsNewer && File.Exists(outputFullPath) && File.GetLastWriteTimeUtc(inputFullPath) < File.GetLastWriteTimeUtc(outputFullPath))
+ {
+ Log.LogMessage(MessageImportance.Low, $"Skipping compression for '{input.ItemSpec}' because '{outputRelativePath}' is newer than '{input.ItemSpec}'.");
+ continue;
+ }
+
+ builder.AppendLine("-s");
+ builder.AppendLine(inputFullPath);
+
+ builder.AppendLine("-o");
+ builder.AppendLine(outputFullPath);
+ }
+
+ return builder.ToString();
+ }
+
+ 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
+ // since it has no crytographic significance.
+ using var hash = SHA1.Create();
+ var bytes = Encoding.UTF8.GetBytes(relativePath);
+ var hashString = Convert.ToBase64String(hash.ComputeHash(bytes));
+
+ var builder = new StringBuilder();
+
+ for (var i = 0; i < 8; i++)
+ {
+ var c = hashString[i];
+ builder.Append(InvalidPathChars.Contains(c) ? '+' : c);
+ }
+
+ builder.Append(extension);
+ return builder.ToString();
+ }
+
+ protected override string GenerateFullPathToTool() => DotNetPath;
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/src/CreateBlazorTrimmerRootDescriptorFile.cs b/src/Components/WebAssembly/Sdk/src/CreateBlazorTrimmerRootDescriptorFile.cs
new file mode 100644
index 0000000000..f14ffad221
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/CreateBlazorTrimmerRootDescriptorFile.cs
@@ -0,0 +1,79 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ // Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/CreateRootDescriptorFile.cs
+ public class CreateBlazorTrimmerRootDescriptorFile : Task
+ {
+ [Required]
+ public ITaskItem[] Assemblies { get; set; }
+
+ [Required]
+ public ITaskItem TrimmerFile { get; set; }
+
+ public override bool Execute()
+ {
+ var rootDescriptor = CreateRootDescriptorContents();
+ if (File.Exists(TrimmerFile.ItemSpec))
+ {
+ var existing = File.ReadAllText(TrimmerFile.ItemSpec);
+
+ if (string.Equals(rootDescriptor, existing, StringComparison.Ordinal))
+ {
+ Log.LogMessage(MessageImportance.Low, "Skipping write to file {0} because contents would not change.", TrimmerFile.ItemSpec);
+ // Avoid writing if the file contents are identical. This is required for build incrementalism.
+ return !Log.HasLoggedErrors;
+ }
+ }
+
+ File.WriteAllText(TrimmerFile.ItemSpec, rootDescriptor);
+ return !Log.HasLoggedErrors;
+ }
+
+ internal string CreateRootDescriptorContents()
+ {
+ var roots = new XElement("linker");
+ foreach (var assembly in Assemblies.OrderBy(a => a.ItemSpec))
+ {
+ // NOTE: Descriptor files don't include the file extension
+ // in the assemblyName.
+ var assemblyName = assembly.GetMetadata("FileName");
+ var typePreserved = assembly.GetMetadata("Preserve");
+ var typeRequired = assembly.GetMetadata("Required");
+
+ var attributes = new List
+ {
+ new XAttribute("fullname", "*"),
+ new XAttribute("required", typeRequired),
+ };
+
+ if (!string.IsNullOrEmpty(typePreserved))
+ {
+ attributes.Add(new XAttribute("preserve", typePreserved));
+ }
+
+ roots.Add(new XElement("assembly",
+ new XAttribute("fullname", assemblyName),
+ new XElement("type", attributes)));
+ }
+
+ var xmlWriterSettings = new XmlWriterSettings
+ {
+ Indent = true,
+ OmitXmlDeclaration = true
+ };
+
+ return new XDocument(roots).Root.ToString();
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/src/GZipCompress.cs b/src/Components/WebAssembly/Sdk/src/GZipCompress.cs
new file mode 100644
index 0000000000..4b8a0c542a
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/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.NET.Sdk.BlazorWebAssembly
+{
+ 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/Components/WebAssembly/Sdk/src/GenerateBlazorWebAssemblyBootJson.cs b/src/Components/WebAssembly/Sdk/src/GenerateBlazorWebAssemblyBootJson.cs
new file mode 100644
index 0000000000..81cb6ecb96
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/GenerateBlazorWebAssemblyBootJson.cs
@@ -0,0 +1,155 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public class GenerateBlazorWebAssemblyBootJson : Task
+ {
+ [Required]
+ public string AssemblyPath { get; set; }
+
+ [Required]
+ public ITaskItem[] Resources { get; set; }
+
+ [Required]
+ public bool DebugBuild { get; set; }
+
+ [Required]
+ public bool LinkerEnabled { get; set; }
+
+ [Required]
+ public bool CacheBootResources { get; set; }
+
+ public ITaskItem[] ConfigurationFiles { get; set; }
+
+ [Required]
+ public string OutputPath { get; set; }
+
+ public ITaskItem[] LazyLoadedAssemblies { get; set; }
+
+ public override bool Execute()
+ {
+ using var fileStream = File.Create(OutputPath);
+ var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
+
+ try
+ {
+ WriteBootJson(fileStream, entryAssemblyName);
+ }
+ catch (Exception ex)
+ {
+ Log.LogErrorFromException(ex);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ // Internal for tests
+ public void WriteBootJson(Stream output, string entryAssemblyName)
+ {
+ var result = new BootJsonData
+ {
+ entryAssembly = entryAssemblyName,
+ cacheBootResources = CacheBootResources,
+ debugBuild = DebugBuild,
+ linkerEnabled = LinkerEnabled,
+ resources = new ResourcesData(),
+ config = new List(),
+ };
+
+ // Build a two-level dictionary of the form:
+ // - assembly:
+ // - UriPath (e.g., "System.Text.Json.dll")
+ // - ContentHash (e.g., "4548fa2e9cf52986")
+ // - runtime:
+ // - UriPath (e.g., "dotnet.js")
+ // - ContentHash (e.g., "3448f339acf512448")
+ if (Resources != null)
+ {
+ var resourceData = result.resources;
+ foreach (var resource in Resources)
+ {
+ ResourceHashesByNameDictionary resourceList;
+
+ var fileName = resource.GetMetadata("FileName");
+ var extension = resource.GetMetadata("Extension");
+ var resourceCulture = resource.GetMetadata("Culture");
+ var assetType = resource.GetMetadata("AssetType");
+ var resourceName = $"{fileName}{extension}";
+
+ if (IsLazyLoadedAssembly(fileName))
+ {
+ resourceData.lazyAssembly ??= new ResourceHashesByNameDictionary();
+ resourceList = resourceData.lazyAssembly;
+ }
+ else if (!string.IsNullOrEmpty(resourceCulture))
+ {
+ resourceData.satelliteResources ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
+ resourceName = resourceCulture + "/" + resourceName;
+
+ if (!resourceData.satelliteResources.TryGetValue(resourceCulture, out resourceList))
+ {
+ resourceList = new ResourceHashesByNameDictionary();
+ resourceData.satelliteResources.Add(resourceCulture, resourceList);
+ }
+ }
+ else if (string.Equals(extension, ".pdb", StringComparison.OrdinalIgnoreCase))
+ {
+ resourceData.pdb ??= new ResourceHashesByNameDictionary();
+ resourceList = resourceData.pdb;
+ }
+ else if (string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase))
+ {
+ resourceList = resourceData.assembly;
+ }
+ else if (string.Equals(assetType, "native", StringComparison.OrdinalIgnoreCase))
+ {
+ resourceList = resourceData.runtime;
+ }
+ else
+ {
+ // This should include items such as XML doc files, which do not need to be recorded in the manifest.
+ continue;
+ }
+
+ if (!resourceList.ContainsKey(resourceName))
+ {
+ resourceList.Add(resourceName, $"sha256-{resource.GetMetadata("FileHash")}");
+ }
+ }
+ }
+
+ if (ConfigurationFiles != null)
+ {
+ foreach (var configFile in ConfigurationFiles)
+ {
+ result.config.Add(Path.GetFileName(configFile.ItemSpec));
+ }
+ }
+
+ var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings
+ {
+ UseSimpleDictionaryFormat = true
+ });
+
+ using var writer = JsonReaderWriterFactory.CreateJsonWriter(output, Encoding.UTF8, ownsStream: false, indent: true);
+ serializer.WriteObject(writer, result);
+ }
+
+ private bool IsLazyLoadedAssembly(string fileName)
+ {
+ return LazyLoadedAssemblies != null && LazyLoadedAssemblies.Any(a => a.ItemSpec == fileName);
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/src/GenerateServiceWorkerAssetsManifest.cs b/src/Components/WebAssembly/Sdk/src/GenerateServiceWorkerAssetsManifest.cs
new file mode 100644
index 0000000000..08c75a927a
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/GenerateServiceWorkerAssetsManifest.cs
@@ -0,0 +1,97 @@
+// 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.Linq;
+using System.Runtime.Serialization.Json;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public partial class GenerateServiceWorkerAssetsManifest : Task
+ {
+ [Required]
+ public ITaskItem[] Assets { get; set; }
+
+ public string Version { get; set; }
+
+ [Required]
+ public string OutputPath { get; set; }
+
+ [Output]
+ public string CalculatedVersion { get; set; }
+
+ public override bool Execute()
+ {
+ using var fileStream = File.Create(OutputPath);
+ CalculatedVersion = GenerateAssetManifest(fileStream);
+
+ return true;
+ }
+
+ internal string GenerateAssetManifest(Stream stream)
+ {
+ var assets = new AssetsManifestFileEntry[Assets.Length];
+ System.Threading.Tasks.Parallel.For(0, assets.Length, i =>
+ {
+ var item = Assets[i];
+ var hash = item.GetMetadata("FileHash");
+ var url = item.GetMetadata("AssetUrl");
+
+ if (string.IsNullOrEmpty(hash))
+ {
+ // Some files that are part of the service worker manifest may not have their hashes previously
+ // calcualted. Calculate them at this time.
+ using var sha = SHA256.Create();
+ using var file = File.OpenRead(item.ItemSpec);
+ var bytes = sha.ComputeHash(file);
+
+ hash = Convert.ToBase64String(bytes);
+ }
+
+ assets[i] = new AssetsManifestFileEntry
+ {
+ hash = "sha256-" + hash,
+ url = url,
+ };
+ });
+
+ var version = Version;
+ if (string.IsNullOrEmpty(version))
+ {
+ // If a version isn't specified (which is likely the most common case), construct a Version by combining
+ // the file names + hashes of all the inputs.
+
+ var combinedHash = string.Join(
+ Environment.NewLine,
+ assets.OrderBy(f => f.url, StringComparer.Ordinal).Select(f => f.hash));
+
+ using var sha = SHA256.Create();
+ var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combinedHash));
+ version = Convert.ToBase64String(bytes).Substring(0, 8);
+ }
+
+ var data = new AssetsManifestFile
+ {
+ version = version,
+ assets = assets,
+ };
+
+ using var streamWriter = new StreamWriter(stream, Encoding.UTF8, bufferSize: 50, leaveOpen: true);
+ streamWriter.Write("self.assetsManifest = ");
+ streamWriter.Flush();
+
+ using var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, ownsStream: false, indent: true);
+ new DataContractJsonSerializer(typeof(AssetsManifestFile)).WriteObject(jsonWriter, data);
+ jsonWriter.Flush();
+
+ streamWriter.WriteLine(";");
+
+ return version;
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/src/Microsoft.NET.Sdk.BlazorWebAssembly.csproj b/src/Components/WebAssembly/Sdk/src/Microsoft.NET.Sdk.BlazorWebAssembly.csproj
new file mode 100644
index 0000000000..adf7330a3f
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/Microsoft.NET.Sdk.BlazorWebAssembly.csproj
@@ -0,0 +1,89 @@
+
+
+ MSBuild support for building Blazor WebAssembly apps.
+ $(DefaultNetCoreTargetFramework);net46
+
+ Microsoft.NET.Sdk.BlazorWebAssembly.Tasks
+ $(MSBuildProjectName).nuspec
+ true
+ $(ArtifactsBinDir)Microsoft.NET.Sdk.BlazorWebAssembly\$(Configuration)\sdk-output\
+
+
+ $(NoWarn);NU5100
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ContinueOnError>true
+ <_Retries>1
+
+
+
+ <_ContinueOnError>false
+ <_Retries>10
+
+
+
+ <_WebAssemblyToolsOutput Include="$(ArtifactsBinDir)Microsoft.NET.Sdk.BlazorWebAssembly.Tools\$(Configuration)\$(DefaultNetCoreTargetFramework)\publish\Microsoft.*" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(PackageTags.Replace(';',' '))
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/Microsoft.NET.Sdk.BlazorWebAssembly.nuspec b/src/Components/WebAssembly/Sdk/src/Microsoft.NET.Sdk.BlazorWebAssembly.nuspec
new file mode 100644
index 0000000000..6d74a1931c
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/Microsoft.NET.Sdk.BlazorWebAssembly.nuspec
@@ -0,0 +1,19 @@
+
+
+
+ $CommonMetadataElements$
+
+
+
+
+
+
+ $CommonFileElements$
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/Sdk/Sdk.props b/src/Components/WebAssembly/Sdk/src/Sdk/Sdk.props
new file mode 100644
index 0000000000..3f6870441f
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/Sdk/Sdk.props
@@ -0,0 +1,22 @@
+
+
+
+ true
+
+
+
+ <_BlazorWebAssemblyPropsFile Condition="'$(_BlazorWebAssemblyPropsFile)' == ''">$(MSBuildThisFileDirectory)..\targets\Microsoft.NET.Sdk.BlazorWebAssembly.Current.props
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/Sdk/Sdk.targets b/src/Components/WebAssembly/Sdk/src/Sdk/Sdk.targets
new file mode 100644
index 0000000000..616c56fb8a
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/Sdk/Sdk.targets
@@ -0,0 +1,20 @@
+
+
+
+
+ <_BlazorWebAssemblyTargetsFile Condition="'$(_BlazorWebAssemblyTargetsFile)' == ''">$(MSBuildThisFileDirectory)..\targets\Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets
+
+
+
+
+
diff --git a/src/Razor/test/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js b/src/Components/WebAssembly/Sdk/src/_._
similarity index 100%
rename from src/Razor/test/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js
rename to src/Components/WebAssembly/Sdk/src/_._
diff --git a/src/Components/WebAssembly/Sdk/src/build/net5.0/Microsoft.NET.Sdk.BlazorWebAssembly.props b/src/Components/WebAssembly/Sdk/src/build/net5.0/Microsoft.NET.Sdk.BlazorWebAssembly.props
new file mode 100644
index 0000000000..27e3fde3e0
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/build/net5.0/Microsoft.NET.Sdk.BlazorWebAssembly.props
@@ -0,0 +1,16 @@
+
+
+
+ <_BlazorWebAssemblyPropsFile>$(MSBuildThisFileDirectory)..\..\targets\Microsoft.NET.Sdk.BlazorWebAssembly.Current.props
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/build/net5.0/Microsoft.NET.Sdk.BlazorWebAssembly.targets b/src/Components/WebAssembly/Sdk/src/build/net5.0/Microsoft.NET.Sdk.BlazorWebAssembly.targets
new file mode 100644
index 0000000000..8091b3d876
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/build/net5.0/Microsoft.NET.Sdk.BlazorWebAssembly.targets
@@ -0,0 +1,16 @@
+
+
+
+ <_BlazorWebAssemblyTargetsFile>$(MSBuildThisFileDirectory)..\..\targets\Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/targets/BlazorWasm.web.config b/src/Components/WebAssembly/Sdk/src/targets/BlazorWasm.web.config
new file mode 100644
index 0000000000..7f9995d792
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/targets/BlazorWasm.web.config
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/targets/LinkerWorkaround.xml b/src/Components/WebAssembly/Sdk/src/targets/LinkerWorkaround.xml
new file mode 100644
index 0000000000..c61bc7a3cc
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/targets/LinkerWorkaround.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props
new file mode 100644
index 0000000000..03b94ad566
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.props
@@ -0,0 +1,36 @@
+
+
+
+
+ browser-wasm
+
+
+ false
+
+ exe
+
+ false
+
+ false
+
+
+
+
+ <_RazorSdkImportsMicrosoftNetSdkRazor Condition="'$(UsingMicrosoftNETSdkRazor)' != 'true'">true
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets
new file mode 100644
index 0000000000..a11bc00625
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.Current.targets
@@ -0,0 +1,582 @@
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)..\..\
+ <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">net5.0
+ <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' != 'Core'">net46
+ <_BlazorWebAssemblySdkTasksAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tasks\$(_BlazorWebAssemblySdkTasksTFM)\Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.dll
+ <_BlazorWebAssemblySdkToolAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\net5.0\Microsoft.NET.Sdk.BlazorWebAssembly.Tools.dll
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+ true
+ link
+
+ /
+ true
+
+
+ false
+ false
+ false
+ false
+ true
+
+
+ <_BlazorOutputPath>wwwroot\_framework\
+
+
+
+
+
+
+
+
+ <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion)
+ <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion)
+ <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js
+ <_BlazorDotNetJsFilePath>$(IntermediateOutputPath)$(_BlazorDotnetJsFileName)
+
+
+
+ <_DotNetJsItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.DestinationSubPath)' == 'dotnet.js' AND '%(ReferenceCopyLocalPaths.AssetType)' == 'native'" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_BlazorSatelliteAssemblyCacheFile>$(IntermediateOutputPath)blazor.satelliteasm.props
+
+ $(OutputPath)$(PublishDirName)\
+
+
+
+ <_BlazorJSFile Include="$(BlazorWebAssemblyJSPath)" />
+ <_BlazorJSFile Include="$(BlazorWebAssemblyJSMapPath)" Condition="Exists('$(BlazorWebAssemblyJSMapPath)')" />
+
+ <_BlazorConfigFile Include="wwwroot\appsettings*.json" />
+
+
+
+
+
+
+ <_BlazorCopyLocalPath
+ Include="@(ReferenceCopyLocalPaths)"
+ Exclude="@(ReferenceSatellitePaths)"/>
+
+ <_BlazorCopyLocalPath Include="@(IntermediateSatelliteAssembliesWithTargetPath)">
+ %(IntermediateSatelliteAssembliesWithTargetPath.Culture)\
+
+
+ <_BlazorOutputWithTargetPath Include="
+ @(_BlazorCopyLocalPath);
+ @(IntermediateAssembly);
+ @(_DebugSymbolsIntermediatePath);
+ @(_BlazorJSFile)" />
+
+ <_BlazorOutputWithTargetPath Include="@(ReferenceSatellitePaths)">
+ $([System.String]::Copy('%(ReferenceSatellitePaths.DestinationSubDirectory)').Trim('\').Trim('/'))
+
+
+
+
+
+
+
+
+
+
+ <_BlazorOutputWithTargetPath
+ Include="@(_BlazorReadSatelliteAssembly)"
+ Exclude="@(_BlazorOutputWithTargetPath)"
+ Condition="'@(_BlazorReadSatelliteAssembly->Count())' != '0'" />
+
+
+ <_BlazorOutputWithTargetPath
+ TargetPath="$(_BlazorOutputPath)%(_BlazorOutputWithTargetPath.DestinationSubDirectory)%(FileName)%(Extension)"
+ Condition="'%(__BlazorOutputWithTargetPath.TargetPath)' == ''" />
+
+
+
+
+
+ <_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'))" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _BlazorWasmPrepareForRun;
+ $(PrepareForRunDependsOn)
+
+
+
+ $(GetCurrentProjectStaticWebAssetsDependsOn);
+ _BlazorWasmPrepareForRun;
+
+
+
+
+
+ <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json
+
+
+
+
+
+
+
+
+
+ <_BlazorWebAssemblyStaticWebAsset Include="$(_BlazorBuildBootJsonPath)">
+ $(PackageId)
+
+ $([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))
+ $(StaticWebAssetBasePath)
+ _framework/blazor.boot.json
+ Never
+
+
+ <_BlazorWebAssemblyStaticWebAsset Include="@(_BlazorOutputWithHash)">
+ $(PackageId)
+
+ $([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))
+ $(StaticWebAssetBasePath)
+ $([System.String]::Copy('%(_BlazorOutputWithHash.TargetPath)').Replace('\','/').Substring(8))
+ Never
+
+
+ <_BlazorWebAssemblyStaticWebAsset Include="@(_BlazorBuildGZipCompressedFile)">
+ $(PackageId)
+
+ $([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))
+ $(StaticWebAssetBasePath)
+ $([System.String]::Copy('%(_BlazorBuildGZipCompressedFile.RelativePath)').Replace('\','/').Substring(8))
+ Never
+
+
+
+ <_ExternalStaticWebAsset Include="@(_BlazorWebAssemblyStaticWebAsset)" SourceType="Generated" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_BlazorTypeGranularTrimmerDescriptorFile>$(IntermediateOutputPath)typegranularity.trimmerdescriptor.xml
+
+
+
+ <_BlazorTypeGranularAssembly
+ Include="@(ManagedAssemblyToLink)"
+ Condition="'%(Extension)' == '.dll' AND ($([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.')) or $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.')))">
+ false
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_BlazorPublishBootResource
+ Include="@(ResolvedFileToPublish)"
+ Condition="$([System.String]::Copy('%(RelativePath)').Replace('\','/').StartsWith('wwwroot/_framework')) AND '%(Extension)' != '.a'" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_CompressedFileOutputPath>$(IntermediateOutputPath)compress\
+ <_BlazorWebAssemblyBrotliIncremental>true
+
+
+
+ <_FileToCompress
+ Include="@(ResolvedFileToPublish)"
+ Condition="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('\','/').StartsWith('wwwroot/'))" />
+
+
+
+
+
+
+
+ <_DotNetHostDirectory>$(NetCoreRoot)
+ <_DotNetHostFileName>dotnet
+ <_DotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_PublishingBlazorWasmProject>true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(RelativePath)
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets
new file mode 100644
index 0000000000..5202eb338d
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/src/targets/Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+ <_ServiceWorkerAssetsManifestIntermediateOutputPath Condition="'$([System.IO.Path]::IsPathRooted($(BaseIntermediateOutputPath)))' == 'true'">obj\$(Configuration)\$(TargetFramework)\$(ServiceWorkerAssetsManifest)
+ <_ServiceWorkerAssetsManifestIntermediateOutputPath Condition="'$([System.IO.Path]::IsPathRooted($(BaseIntermediateOutputPath)))' != 'true'">$(IntermediateOutputPath)$(ServiceWorkerAssetsManifest)
+ <_ServiceWorkerAssetsManifestFullPath>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/$(_ServiceWorkerAssetsManifestIntermediateOutputPath)'))
+
+
+
+ <_ManifestStaticWebAsset Include="$(_ServiceWorkerAssetsManifestFullPath)">
+
+ $(PackageId)
+ $([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))
+ $(StaticWebAssetBasePath)
+ $(ServiceWorkerAssetsManifest)
+ Never
+
+
+
+ <_ServiceWorkerIntermediateFile Include="@(ServiceWorker->'$(IntermediateOutputPath)serviceworkers\%(Identity)')">
+ %(ServiceWorker.PublishedContent)
+ %(ServiceWorker.Identity)
+ %(ServiceWorker.Identity)
+ %(ServiceWorker.Identity)
+ $([System.String]::Copy('%(ServiceWorker.Identity)').Substring(8))
+
+
+ <_ServiceWorkerStaticWebAsset Include="%(_ServiceWorkerIntermediateFile.FullPath)">
+
+ $(PackageId)
+ $([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))
+ $(StaticWebAssetBasePath)
+ %(TargetOutputPath)
+ Never
+
+
+
+
+
+
+
+
+
+
+ <_ServiceWorkItem Include="@(StaticWebAsset)" Exclude="$(_ServiceWorkerAssetsManifestFullPath);@(_ServiceWorkerStaticWebAsset)">
+ $([System.String]::Copy('$([System.String]::Copy('%(StaticWebAsset.BasePath)').TrimEnd('/'))/%(StaticWebAsset.RelativePath)').Replace('\','/').TrimStart('/'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ServiceWorkerAssetsManifestPublishIntermediateOutputPath>$(IntermediateOutputPath)publish-$(ServiceWorkerAssetsManifest)
+
+
+
+ <_ServiceWorkerIntermediatePublishFile Include="$(IntermediateOutputPath)serviceworkers\%(FileName).publish%(Extension)">
+ %(ServiceWorker.PublishedContent)
+ %(ServiceWorker.Identity)
+ %(ServiceWorker.Identity)
+
+
+ <_ServiceWorkerPublishFile Include="@(ResolvedFileToPublish)" Condition="$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('\','/').StartsWith('wwwroot/'))">
+ $([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('\','/').TrimStart('/'))
+ $([System.String]::Copy('%(RelativePath)').Replace('\','/').Substring(8))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/test/BlazorReadSatelliteAssemblyFileTest.cs b/src/Components/WebAssembly/Sdk/test/BlazorReadSatelliteAssemblyFileTest.cs
new file mode 100644
index 0000000000..b95a6154b6
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/test/BlazorReadSatelliteAssemblyFileTest.cs
@@ -0,0 +1,68 @@
+// 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.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Moq;
+using Xunit;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public class BlazorReadSatelliteAssemblyFileTest
+ {
+ [Fact]
+ public void WritesAndReadsRoundTrip()
+ {
+ // Arrange/Act
+ var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+
+ var writer = new BlazorWriteSatelliteAssemblyFile
+ {
+ BuildEngine = Mock.Of(),
+ WriteFile = new TaskItem(tempFile),
+ SatelliteAssembly = new[]
+ {
+ new TaskItem("Resources.fr.dll", new Dictionary
+ {
+ ["Culture"] = "fr",
+ ["DestinationSubDirectory"] = "fr\\",
+ }),
+ new TaskItem("Resources.ja-jp.dll", new Dictionary
+ {
+ ["Culture"] = "ja-jp",
+ ["DestinationSubDirectory"] = "ja-jp\\",
+ }),
+ },
+ };
+
+ var reader = new BlazorReadSatelliteAssemblyFile
+ {
+ BuildEngine = Mock.Of(),
+ ReadFile = new TaskItem(tempFile),
+ };
+
+ writer.Execute();
+
+ Assert.True(File.Exists(tempFile), "Write should have succeeded.");
+
+ reader.Execute();
+
+ Assert.Collection(
+ reader.SatelliteAssembly,
+ assembly =>
+ {
+ Assert.Equal("Resources.fr.dll", assembly.ItemSpec);
+ Assert.Equal("fr", assembly.GetMetadata("Culture"));
+ Assert.Equal("fr\\", assembly.GetMetadata("DestinationSubDirectory"));
+ },
+ assembly =>
+ {
+ Assert.Equal("Resources.ja-jp.dll", assembly.ItemSpec);
+ Assert.Equal("ja-jp", assembly.GetMetadata("Culture"));
+ Assert.Equal("ja-jp\\", assembly.GetMetadata("DestinationSubDirectory"));
+ });
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/test/GenerateBlazorBootJsonTest.cs b/src/Components/WebAssembly/Sdk/test/GenerateBlazorBootJsonTest.cs
new file mode 100644
index 0000000000..139f22f27f
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/test/GenerateBlazorBootJsonTest.cs
@@ -0,0 +1,195 @@
+// 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.IO;
+using System.Linq;
+using System.Runtime.Serialization.Json;
+using Microsoft.Build.Framework;
+using Moq;
+using Xunit;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly
+{
+ public class GenerateBlazorWebAssemblyBootJsonTest
+ {
+ [Fact]
+ public void GroupsResourcesByType()
+ {
+ // Arrange
+ var taskInstance = new GenerateBlazorWebAssemblyBootJson
+ {
+ AssemblyPath = "MyApp.Entrypoint.dll",
+ Resources = new[]
+ {
+ CreateResourceTaskItem(
+ ("FileName", "My.Assembly1"),
+ ("Extension", ".dll"),
+ ("FileHash", "abcdefghikjlmnopqrstuvwxyz")),
+
+ CreateResourceTaskItem(
+ ("FileName", "My.Assembly2"),
+ ("Extension", ".dll"),
+ ("FileHash", "012345678901234567890123456789")),
+
+ CreateResourceTaskItem(
+ ("FileName", "SomePdb"),
+ ("Extension", ".pdb"),
+ ("FileHash", "pdbhashpdbhashpdbhash")),
+
+ CreateResourceTaskItem(
+ ("FileName", "My.Assembly1"),
+ ("Extension", ".pdb"),
+ ("FileHash", "pdbdefghikjlmnopqrstuvwxyz")),
+
+ CreateResourceTaskItem(
+ ("FileName", "some-runtime-file"),
+ ("FileHash", "runtimehashruntimehash"),
+ ("AssetType", "native")),
+
+ CreateResourceTaskItem(
+ ("FileName", "satellite-assembly1"),
+ ("Extension", ".dll"),
+ ("FileHash", "hashsatelliteassembly1"),
+ ("Culture", "en-GB")),
+
+ CreateResourceTaskItem(
+ ("FileName", "satellite-assembly2"),
+ ("Extension", ".dll"),
+ ("FileHash", "hashsatelliteassembly2"),
+ ("Culture", "fr")),
+
+ CreateResourceTaskItem(
+ ("FileName", "satellite-assembly3"),
+ ("Extension", ".dll"),
+ ("FileHash", "hashsatelliteassembly3"),
+ ("Culture", "en-GB")),
+ }
+ };
+
+ using var stream = new MemoryStream();
+
+ // Act
+ taskInstance.WriteBootJson(stream, "MyEntrypointAssembly");
+
+ // Assert
+ var parsedContent = ParseBootData(stream);
+ Assert.Equal("MyEntrypointAssembly", parsedContent.entryAssembly);
+
+ var resources = parsedContent.resources.assembly;
+ Assert.Equal(2, resources.Count);
+ Assert.Equal("sha256-abcdefghikjlmnopqrstuvwxyz", resources["My.Assembly1.dll"]);
+ Assert.Equal("sha256-012345678901234567890123456789", resources["My.Assembly2.dll"]);
+
+ resources = parsedContent.resources.pdb;
+ Assert.Equal(2, resources.Count);
+ Assert.Equal("sha256-pdbhashpdbhashpdbhash", resources["SomePdb.pdb"]);
+ Assert.Equal("sha256-pdbdefghikjlmnopqrstuvwxyz", resources["My.Assembly1.pdb"]);
+
+ resources = parsedContent.resources.runtime;
+ Assert.Single(resources);
+ Assert.Equal("sha256-runtimehashruntimehash", resources["some-runtime-file"]);
+
+ var satelliteResources = parsedContent.resources.satelliteResources;
+ Assert.Collection(
+ satelliteResources.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("en-GB", kvp.Key);
+ Assert.Collection(
+ kvp.Value.OrderBy(item => item.Key),
+ item =>
+ {
+ Assert.Equal("en-GB/satellite-assembly1.dll", item.Key);
+ Assert.Equal("sha256-hashsatelliteassembly1", item.Value);
+ },
+ item =>
+ {
+ Assert.Equal("en-GB/satellite-assembly3.dll", item.Key);
+ Assert.Equal("sha256-hashsatelliteassembly3", item.Value);
+ });
+ },
+ kvp =>
+ {
+ Assert.Equal("fr", kvp.Key);
+ Assert.Collection(
+ kvp.Value.OrderBy(item => item.Key),
+ item =>
+ {
+ Assert.Equal("fr/satellite-assembly2.dll", item.Key);
+ Assert.Equal("sha256-hashsatelliteassembly2", item.Value);
+ });
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CanSpecifyCacheBootResources(bool flagValue)
+ {
+ // Arrange
+ var taskInstance = new GenerateBlazorWebAssemblyBootJson { CacheBootResources = flagValue };
+ using var stream = new MemoryStream();
+
+ // Act
+ taskInstance.WriteBootJson(stream, "MyEntrypointAssembly");
+
+ // Assert
+ var parsedContent = ParseBootData(stream);
+ Assert.Equal(flagValue, parsedContent.cacheBootResources);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CanSpecifyDebugBuild(bool flagValue)
+ {
+ // Arrange
+ var taskInstance = new GenerateBlazorWebAssemblyBootJson { DebugBuild = flagValue };
+ using var stream = new MemoryStream();
+
+ // Act
+ taskInstance.WriteBootJson(stream, "MyEntrypointAssembly");
+
+ // Assert
+ var parsedContent = ParseBootData(stream);
+ Assert.Equal(flagValue, parsedContent.debugBuild);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CanSpecifyLinkerEnabled(bool flagValue)
+ {
+ // Arrange
+ var taskInstance = new GenerateBlazorWebAssemblyBootJson { LinkerEnabled = flagValue };
+ using var stream = new MemoryStream();
+
+ // Act
+ taskInstance.WriteBootJson(stream, "MyEntrypointAssembly");
+
+ // Assert
+ var parsedContent = ParseBootData(stream);
+ Assert.Equal(flagValue, parsedContent.linkerEnabled);
+ }
+
+ private static BootJsonData ParseBootData(Stream stream)
+ {
+ stream.Position = 0;
+ var serializer = new DataContractJsonSerializer(
+ typeof(BootJsonData),
+ new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true });
+ return (BootJsonData)serializer.ReadObject(stream);
+ }
+
+ private static ITaskItem CreateResourceTaskItem(params (string key, string value)[] values)
+ {
+ var mock = new Mock();
+
+ foreach (var (key, value) in values)
+ {
+ mock.Setup(m => m.GetMetadata(key)).Returns(value);
+ }
+ return mock.Object;
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj b/src/Components/WebAssembly/Sdk/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj
new file mode 100644
index 0000000000..5d30b4f781
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj
@@ -0,0 +1,12 @@
+
+
+
+ $(DefaultNetCoreTargetFramework)
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/testassets/Directory.Build.props b/src/Components/WebAssembly/Sdk/testassets/Directory.Build.props
new file mode 100644
index 0000000000..6d0949542f
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/testassets/Directory.Build.props
@@ -0,0 +1,50 @@
+
+
+
+
+
+ false
+ true
+
+
+ false
+
+ net5.0
+
+ $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, 'AspNetCore.sln'))\
+
+ $(RepoRoot)src\Razor\Microsoft.NET.Sdk.Razor\src\build\netstandard2.0\Sdk.Razor.CurrentVersion.props
+ $(RepoRoot)src\Razor\Microsoft.NET.Sdk.Razor\src\build\netstandard2.0\Sdk.Razor.CurrentVersion.targets
+ $(RepoRoot)artifacts\bin\Microsoft.NET.Sdk.Razor\
+ $(RepoRoot)artifacts\bin\Microsoft.NET.Sdk.BlazorWebAssembly\
+ $(MSBuildThisFileDirectory)blazor.webassembly.js
+
+
+
+
+
+
+ 1.0.0
+
+
+ <_BlazorBrotliCompressionLevel>NoCompression
+
+
+
+
+ <_MvcAssemblyName Include="Microsoft.AspNetCore.Razor.Test.MvcShim.ClassLib" />
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/testassets/Directory.Build.targets b/src/Components/WebAssembly/Sdk/testassets/Directory.Build.targets
new file mode 100644
index 0000000000..45a2ee1e56
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/testassets/Directory.Build.targets
@@ -0,0 +1,7 @@
+
+
+ $(MicrosoftNETCoreAppRuntimeVersion)
+
+ 99.9
+
+
diff --git a/src/Razor/test/testassets/LinkBaseToWebRoot/js/LinkedScript.js b/src/Components/WebAssembly/Sdk/testassets/LinkBaseToWebRoot/js/LinkedScript.js
similarity index 100%
rename from src/Razor/test/testassets/LinkBaseToWebRoot/js/LinkedScript.js
rename to src/Components/WebAssembly/Sdk/testassets/LinkBaseToWebRoot/js/LinkedScript.js
diff --git a/src/Components/WebAssembly/Sdk/testassets/RestoreBlazorWasmTestProjects/RestoreBlazorWasmTestProjects.csproj b/src/Components/WebAssembly/Sdk/testassets/RestoreBlazorWasmTestProjects/RestoreBlazorWasmTestProjects.csproj
new file mode 100644
index 0000000000..f4debf8088
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/testassets/RestoreBlazorWasmTestProjects/RestoreBlazorWasmTestProjects.csproj
@@ -0,0 +1,12 @@
+
+
+ $(DefaultNetCoreTargetFramework)
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/testassets/blazor.webassembly.js b/src/Components/WebAssembly/Sdk/testassets/blazor.webassembly.js
new file mode 100644
index 0000000000..84362ca046
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/testassets/blazor.webassembly.js
@@ -0,0 +1 @@
+Test file
\ No newline at end of file
diff --git a/src/Razor/test/testassets/blazorhosted-rid/Program.cs b/src/Components/WebAssembly/Sdk/testassets/blazorhosted-rid/Program.cs
similarity index 100%
rename from src/Razor/test/testassets/blazorhosted-rid/Program.cs
rename to src/Components/WebAssembly/Sdk/testassets/blazorhosted-rid/Program.cs
diff --git a/src/Razor/test/testassets/blazorhosted-rid/blazorhosted-rid.csproj b/src/Components/WebAssembly/Sdk/testassets/blazorhosted-rid/blazorhosted-rid.csproj
similarity index 100%
rename from src/Razor/test/testassets/blazorhosted-rid/blazorhosted-rid.csproj
rename to src/Components/WebAssembly/Sdk/testassets/blazorhosted-rid/blazorhosted-rid.csproj
diff --git a/src/Razor/test/testassets/blazorhosted/Program.cs b/src/Components/WebAssembly/Sdk/testassets/blazorhosted/Program.cs
similarity index 100%
rename from src/Razor/test/testassets/blazorhosted/Program.cs
rename to src/Components/WebAssembly/Sdk/testassets/blazorhosted/Program.cs
diff --git a/src/Razor/test/testassets/blazorhosted/blazorhosted.csproj b/src/Components/WebAssembly/Sdk/testassets/blazorhosted/blazorhosted.csproj
similarity index 100%
rename from src/Razor/test/testassets/blazorhosted/blazorhosted.csproj
rename to src/Components/WebAssembly/Sdk/testassets/blazorhosted/blazorhosted.csproj
diff --git a/src/Razor/test/testassets/blazorwasm/App.razor b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/App.razor
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/App.razor
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/App.razor
diff --git a/src/Razor/test/testassets/blazorwasm/LinkToWebRoot/css/app.css b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/LinkToWebRoot/css/app.css
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/LinkToWebRoot/css/app.css
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/LinkToWebRoot/css/app.css
diff --git a/src/Razor/test/testassets/blazorwasm/Pages/Index.razor b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Pages/Index.razor
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/Pages/Index.razor
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/Pages/Index.razor
diff --git a/src/Razor/test/testassets/blazorwasm/Program.cs b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Program.cs
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/Program.cs
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/Program.cs
diff --git a/src/Razor/test/testassets/blazorwasm/Resources.ja.resx.txt b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/Resources.ja.resx.txt
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/Resources.ja.resx.txt
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/Resources.ja.resx.txt
diff --git a/src/Razor/test/testassets/blazorwasm/_Imports.razor b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/_Imports.razor
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/_Imports.razor
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/_Imports.razor
diff --git a/src/Razor/test/testassets/blazorwasm/blazorwasm.csproj b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/blazorwasm.csproj
similarity index 88%
rename from src/Razor/test/testassets/blazorwasm/blazorwasm.csproj
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/blazorwasm.csproj
index 36a511e7c7..c8906b1bd2 100644
--- a/src/Razor/test/testassets/blazorwasm/blazorwasm.csproj
+++ b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/blazorwasm.csproj
@@ -1,10 +1,12 @@
+
+
+
net5.0
- true
browser-wasm
- false
$(RazorSdkArtifactsDirectory)$(Configuration)\sdk-output\
+ $(BlazorWebAssemblySdkArtifactsDirectory)$(Configuration)\sdk-output\
custom-service-worker-assets.js
@@ -50,4 +52,6 @@
+
+
diff --git a/src/Razor/test/testassets/blazorwasm/wwwroot/Fake-License.txt b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/Fake-License.txt
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/wwwroot/Fake-License.txt
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/Fake-License.txt
diff --git a/src/Razor/test/testassets/blazorwasm/wwwroot/css/app.css b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/css/app.css
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/wwwroot/css/app.css
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/css/app.css
diff --git a/src/Razor/test/testassets/blazorwasm/wwwroot/index.html b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/index.html
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/wwwroot/index.html
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/index.html
diff --git a/src/Razor/test/testassets/blazorwasm/wwwroot/serviceworkers/my-prod-service-worker.js b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/serviceworkers/my-prod-service-worker.js
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/wwwroot/serviceworkers/my-prod-service-worker.js
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/serviceworkers/my-prod-service-worker.js
diff --git a/src/Razor/test/testassets/blazorwasm/wwwroot/serviceworkers/my-service-worker.js b/src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/serviceworkers/my-service-worker.js
similarity index 100%
rename from src/Razor/test/testassets/blazorwasm/wwwroot/serviceworkers/my-service-worker.js
rename to src/Components/WebAssembly/Sdk/testassets/blazorwasm/wwwroot/serviceworkers/my-service-worker.js
diff --git a/src/Razor/test/testassets/classlibrarywithsatelliteassemblies/Class1.cs b/src/Components/WebAssembly/Sdk/testassets/classlibrarywithsatelliteassemblies/Class1.cs
similarity index 100%
rename from src/Razor/test/testassets/classlibrarywithsatelliteassemblies/Class1.cs
rename to src/Components/WebAssembly/Sdk/testassets/classlibrarywithsatelliteassemblies/Class1.cs
diff --git a/src/Razor/test/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx b/src/Components/WebAssembly/Sdk/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx
similarity index 100%
rename from src/Razor/test/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx
rename to src/Components/WebAssembly/Sdk/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx
diff --git a/src/Razor/test/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj b/src/Components/WebAssembly/Sdk/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj
similarity index 100%
rename from src/Razor/test/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj
rename to src/Components/WebAssembly/Sdk/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj
diff --git a/src/Razor/test/testassets/razorclasslibrary/Class1.cs b/src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/Class1.cs
similarity index 100%
rename from src/Razor/test/testassets/razorclasslibrary/Class1.cs
rename to src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/Class1.cs
diff --git a/src/Razor/test/testassets/razorclasslibrary/RazorClassLibrary.csproj b/src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/RazorClassLibrary.csproj
similarity index 100%
rename from src/Razor/test/testassets/razorclasslibrary/RazorClassLibrary.csproj
rename to src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/RazorClassLibrary.csproj
diff --git a/src/Razor/test/testassets/razorclasslibrary/wwwroot/styles.css b/src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/wwwroot/styles.css
similarity index 100%
rename from src/Razor/test/testassets/razorclasslibrary/wwwroot/styles.css
rename to src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/wwwroot/styles.css
diff --git a/src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js b/src/Components/WebAssembly/Sdk/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/Components/WebAssembly/Sdk/tools/Application.cs b/src/Components/WebAssembly/Sdk/tools/Application.cs
new file mode 100644
index 0000000000..d9d66d10bc
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/tools/Application.cs
@@ -0,0 +1,98 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tools
+{
+ internal class Application : CommandLineApplication
+ {
+ public Application(
+ CancellationToken cancellationToken,
+ TextWriter output = null,
+ TextWriter error = null)
+ {
+ CancellationToken = cancellationToken;
+ Out = output ?? Out;
+ Error = error ?? Error;
+
+ Name = "BlazorWebAssembly.Tools";
+ FullName = "Microsoft Blazor WebAssembly SDK tool";
+ Description = "CLI for Blazor WebAssembly operations.";
+ ShortVersionGetter = GetInformationalVersion;
+
+ HelpOption("-?|-h|--help");
+
+ Commands.Add(new BrotliCompressCommand(this));
+ }
+
+ public CancellationToken CancellationToken { get; }
+
+ public new int Execute(params string[] args)
+ {
+ try
+ {
+ return base.Execute(ExpandResponseFiles(args));
+ }
+ catch (AggregateException ex) when (ex.InnerException != null)
+ {
+ foreach (var innerException in ex.Flatten().InnerExceptions)
+ {
+ Error.WriteLine(innerException.Message);
+ Error.WriteLine(innerException.StackTrace);
+ }
+ return 1;
+ }
+ catch (CommandParsingException ex)
+ {
+ // Don't show a call stack when we have unneeded arguments, just print the error message.
+ // The code that throws this exception will print help, so no need to do it here.
+ Error.WriteLine(ex.Message);
+ return 1;
+ }
+ catch (OperationCanceledException)
+ {
+ // This is a cancellation, not a failure.
+ Error.WriteLine("Cancelled");
+ return 1;
+ }
+ catch (Exception ex)
+ {
+ Error.WriteLine(ex.Message);
+ Error.WriteLine(ex.StackTrace);
+ return 1;
+ }
+ }
+
+ private string GetInformationalVersion()
+ {
+ var assembly = typeof(Application).GetTypeInfo().Assembly;
+ var attribute = assembly.GetCustomAttribute();
+ return attribute.InformationalVersion;
+ }
+
+ private static string[] ExpandResponseFiles(string[] args)
+ {
+ var expandedArgs = new List();
+ foreach (var arg in args)
+ {
+ if (!arg.StartsWith("@", StringComparison.Ordinal))
+ {
+ expandedArgs.Add(arg);
+ }
+ else
+ {
+ var fileName = arg.Substring(1);
+ expandedArgs.AddRange(File.ReadLines(fileName));
+ }
+ }
+
+ return expandedArgs.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/WebAssembly/Sdk/tools/BrotliCompressCommand.cs b/src/Components/WebAssembly/Sdk/tools/BrotliCompressCommand.cs
new file mode 100644
index 0000000000..136c2fc660
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/tools/BrotliCompressCommand.cs
@@ -0,0 +1,85 @@
+// 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.Threading.Tasks;
+using Microsoft.Extensions.CommandLineUtils;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tools
+{
+ internal class BrotliCompressCommand : CommandLineApplication
+ {
+ public BrotliCompressCommand(Application parent)
+ : base(throwOnUnexpectedArg: true)
+ {
+ base.Parent = parent;
+ Name = "brotli";
+ Sources = Option("-s", "files to compress", CommandOptionType.MultipleValue);
+ Outputs = Option("-o", "Output file path", CommandOptionType.MultipleValue);
+ CompressionLevelOption = Option("-c", "Compression level", CommandOptionType.SingleValue);
+
+ Invoke = () => Execute().GetAwaiter().GetResult();
+ }
+
+ public CommandOption Sources { get; }
+
+ public CommandOption Outputs { get; }
+
+ public CommandOption CompressionLevelOption { get; }
+
+ public CompressionLevel CompressionLevel { get; private set; } = CompressionLevel.Optimal;
+
+ private Task Execute()
+ {
+ if (!ValidateArguments())
+ {
+ ShowHelp();
+ return Task.FromResult(1);
+ }
+
+ return ExecuteCoreAsync();
+ }
+
+ private bool ValidateArguments()
+ {
+ if (Sources.Values.Count != Outputs.Values.Count)
+ {
+ Error.WriteLine($"{Sources.Description} has {Sources.Values.Count}, but {Outputs.Description} has {Outputs.Values.Count} values.");
+ return false;
+ }
+
+ if (CompressionLevelOption.HasValue())
+ {
+ if (!Enum.TryParse(CompressionLevelOption.Value(), out var value))
+ {
+ Error.WriteLine($"Invalid option {CompressionLevelOption.Value()} for {CompressionLevelOption.Template}.");
+ return false;
+ }
+
+ CompressionLevel = value;
+ }
+
+ return true;
+ }
+
+ private Task ExecuteCoreAsync()
+ {
+ Parallel.For(0, Sources.Values.Count, i =>
+ {
+ var source = Sources.Values[i];
+ var output = Outputs.Values[i];
+
+ using var sourceStream = File.OpenRead(source);
+ using var fileStream = new FileStream(output, FileMode.Create);
+
+ using var stream = new BrotliStream(fileStream, CompressionLevel);
+
+ sourceStream.CopyTo(stream);
+ });
+
+ return Task.FromResult(0);
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/tools/DebugMode.cs b/src/Components/WebAssembly/Sdk/tools/DebugMode.cs
new file mode 100644
index 0000000000..816bb4a783
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/tools/DebugMode.cs
@@ -0,0 +1,27 @@
+// 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.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tools
+{
+ internal static class DebugMode
+ {
+ public static void HandleDebugSwitch(ref string[] args)
+ {
+ if (args.Length > 0 && string.Equals("--debug", args[0], StringComparison.OrdinalIgnoreCase))
+ {
+ args = args.Skip(1).ToArray();
+
+ Console.WriteLine("Waiting for debugger in pid: {0}", Process.GetCurrentProcess().Id);
+ while (!Debugger.IsAttached)
+ {
+ Thread.Sleep(TimeSpan.FromSeconds(3));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/WebAssembly/Sdk/tools/Microsoft.NET.Sdk.BlazorWebAssembly.Tools.csproj b/src/Components/WebAssembly/Sdk/tools/Microsoft.NET.Sdk.BlazorWebAssembly.Tools.csproj
new file mode 100644
index 0000000000..6ac7a26c19
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/tools/Microsoft.NET.Sdk.BlazorWebAssembly.Tools.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net5.0
+
+ false
+ false
+ true
+
+ false
+
+
+
+ false
+
+
+
+
+
+
+
diff --git a/src/Components/WebAssembly/Sdk/tools/Program.cs b/src/Components/WebAssembly/Sdk/tools/Program.cs
new file mode 100644
index 0000000000..b00093323d
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/tools/Program.cs
@@ -0,0 +1,30 @@
+// 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.Threading;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tools
+{
+ internal static class Program
+ {
+ public static int Main(string[] args)
+ {
+ DebugMode.HandleDebugSwitch(ref args);
+
+ var cancel = new CancellationTokenSource();
+ Console.CancelKeyPress += (sender, e) => { cancel.Cancel(); };
+
+ var application = new Application(
+ cancel.Token,
+ Console.Out,
+ Console.Error);
+
+ application.Commands.Add(new BrotliCompressCommand(application));
+
+ return application.Execute(args);
+ }
+ }
+}
diff --git a/src/Components/WebAssembly/Sdk/tools/runtimeconfig.template.json b/src/Components/WebAssembly/Sdk/tools/runtimeconfig.template.json
new file mode 100644
index 0000000000..2c73f39890
--- /dev/null
+++ b/src/Components/WebAssembly/Sdk/tools/runtimeconfig.template.json
@@ -0,0 +1,3 @@
+{
+ "rollForwardOnNoCandidateFx": 2
+}
\ No newline at end of file
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Microsoft.NET.Sdk.Razor.IntegrationTests.csproj b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Microsoft.NET.Sdk.Razor.IntegrationTests.csproj
index a1e160322e..2832be128d 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Microsoft.NET.Sdk.Razor.IntegrationTests.csproj
+++ b/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Microsoft.NET.Sdk.Razor.IntegrationTests.csproj
@@ -1,4 +1,4 @@
-
+
false
+ $(MSBuildProjectDirectory)\..\..\test\testassets\
+
+
@@ -24,49 +27,6 @@
-
-
- <_Parameter1>Testing.AdditionalRestoreSources
- <_Parameter2>$(MSBuildThisFileDirectory)..\testassets\PregeneratedPackages
-
-
-
- <_Parameter1>ArtifactsLogDir
- <_Parameter2>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log', '$(_BuildConfig)'))
-
-
-
- <_Parameter1>ProcDumpToolPath
- <_Parameter2>$(ProcDumpPath)
-
-
-
- <_Parameter1>Testing.RepoRoot
- <_Parameter2>$(RepoRoot)
-
-
-
- <_Parameter1>MicrosoftNETCoreAppRuntimeVersion
- <_Parameter2>$(MicrosoftNETCoreAppRuntimeVersion)
-
-
-
- <_Parameter1>DefaultNetCoreTargetFramework
- <_Parameter2>$(DefaultNetCoreTargetFramework)
-
-
-
- <_Parameter1>MicrosoftNetCompilersToolsetPackageVersion
- <_Parameter2>$(MicrosoftNetCompilersToolsetPackageVersion)
-
-
-
- <_Parameter1>RazorSdkDirectoryRoot
- <_Parameter2>$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\sdk-output\
-
-
-
-
diff --git a/src/Shared/E2ETesting/E2ETesting.targets b/src/Shared/E2ETesting/E2ETesting.targets
index d0fa6cb856..76ced2cce9 100644
--- a/src/Shared/E2ETesting/E2ETesting.targets
+++ b/src/Shared/E2ETesting/E2ETesting.targets
@@ -66,14 +66,9 @@
<_DefaultProjectRoot>$([System.IO.Path]::GetFullPath($(_DefaultProjectFilter)))
- <_ContentRootProjectReferencesUnfiltered
- Include="@(ReferencePath)"
- Condition="'%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference'" />
- <_ContentRootProjectReferencesFilter
- Include="@(_ContentRootProjectReferencesUnfiltered->StartsWith('$(_DefaultProjectRoot)'))" />
<_ContentRootProjectReferences
- Include="@(_ContentRootProjectReferencesFilter)"
- Condition="'%(Identity)' == 'True'" />
+ Include="@(ReferencePath)"
+ Condition="'%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference' AND $([System.String]::Copy(%(ReferencePath.MSBuildSourceProjectFile)).StartsWith('$(_DefaultProjectRoot)'))" />
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Assert.cs b/src/Shared/MSBuild.Testing/Assert.cs
similarity index 100%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/Assert.cs
rename to src/Shared/MSBuild.Testing/Assert.cs
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildVariables.cs b/src/Shared/MSBuild.Testing/BuildVariables.cs
similarity index 83%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildVariables.cs
rename to src/Shared/MSBuild.Testing/BuildVariables.cs
index 797d244625..b659ba918c 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/BuildVariables.cs
+++ b/src/Shared/MSBuild.Testing/BuildVariables.cs
@@ -19,8 +19,12 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
public static string RazorSdkDirectoryRoot => TestAssemblyMetadata.SingleOrDefault(a => a.Key == "RazorSdkDirectoryRoot").Value;
+ public static string BlazorWebAssemblySdkDirectoryRoot => TestAssemblyMetadata.SingleOrDefault(a => a.Key == "BlazorWebAssemblySdkDirectoryRoot").Value;
+
public static string RepoRoot => TestAssemblyMetadata.SingleOrDefault(a => a.Key == "Testing.RepoRoot").Value;
-
+
public static string DefaultNetCoreTargetFramework => TestAssemblyMetadata.SingleOrDefault(a => a.Key == "DefaultNetCoreTargetFramework").Value;
+
+ public static string TestAppsRoot => TestAssemblyMetadata.SingleOrDefault(a => a.Key == "TestAppsRoot").Value;
}
}
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/FIleThumbPrint.cs b/src/Shared/MSBuild.Testing/FIleThumbPrint.cs
similarity index 100%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/FIleThumbPrint.cs
rename to src/Shared/MSBuild.Testing/FIleThumbPrint.cs
diff --git a/src/Shared/MSBuild.Testing/MSBuild.Testing.targets b/src/Shared/MSBuild.Testing/MSBuild.Testing.targets
new file mode 100644
index 0000000000..8868a23d97
--- /dev/null
+++ b/src/Shared/MSBuild.Testing/MSBuild.Testing.targets
@@ -0,0 +1,53 @@
+
+
+
+
+ <_Parameter1>ArtifactsLogDir
+ <_Parameter2>$([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'log', '$(_BuildConfig)'))
+
+
+
+ <_Parameter1>ProcDumpToolPath
+ <_Parameter2>$(ProcDumpPath)
+
+
+
+ <_Parameter1>Testing.RepoRoot
+ <_Parameter2>$(RepoRoot)
+
+
+
+ <_Parameter1>MicrosoftNETCoreAppRuntimeVersion
+ <_Parameter2>$(MicrosoftNETCoreAppRuntimeVersion)
+
+
+
+ <_Parameter1>DefaultNetCoreTargetFramework
+ <_Parameter2>$(DefaultNetCoreTargetFramework)
+
+
+
+ <_Parameter1>MicrosoftNetCompilersToolsetPackageVersion
+ <_Parameter2>$(MicrosoftNetCompilersToolsetPackageVersion)
+
+
+
+ <_Parameter1>RazorSdkDirectoryRoot
+ <_Parameter2>$(ArtifactsBinDir)Microsoft.NET.Sdk.Razor\$(Configuration)\sdk-output\
+
+
+
+ <_Parameter1>BlazorWebAssemblySdkDirectoryRoot
+ <_Parameter2>$(ArtifactsBinDir)Microsoft.NET.Sdk.BlazorWebAssembly\$(Configuration)\sdk-output\
+
+
+
+ <_Parameter1>TestAppsRoot
+ <_Parameter2>$(TestAppsRoot)
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildProcessKind.cs b/src/Shared/MSBuild.Testing/MSBuildProcessKind.cs
similarity index 100%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildProcessKind.cs
rename to src/Shared/MSBuild.Testing/MSBuildProcessKind.cs
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildProcessManager.cs b/src/Shared/MSBuild.Testing/MSBuildProcessManager.cs
similarity index 98%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildProcessManager.cs
rename to src/Shared/MSBuild.Testing/MSBuildProcessManager.cs
index cc79523d5b..3d9ada158b 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildProcessManager.cs
+++ b/src/Shared/MSBuild.Testing/MSBuildProcessManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -37,6 +37,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
$"/p:MicrosoftNETCoreAppRuntimeVersion={BuildVariables.MicrosoftNETCoreAppRuntimeVersion}",
$"/p:MicrosoftNetCompilersToolsetPackageVersion={BuildVariables.MicrosoftNetCompilersToolsetPackageVersion}",
$"/p:RazorSdkDirectoryRoot={BuildVariables.RazorSdkDirectoryRoot}",
+ $"/p:BlazorWebAssemblySdkDirectoryRoot={BuildVariables.BlazorWebAssemblySdkDirectoryRoot}",
$"/p:RepoRoot={BuildVariables.RepoRoot}",
$"/p:Configuration={project.Configuration}",
$"/t:{target}",
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildResult.cs b/src/Shared/MSBuild.Testing/MSBuildResult.cs
similarity index 100%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/MSBuildResult.cs
rename to src/Shared/MSBuild.Testing/MSBuildResult.cs
diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/ProjectDirectory.cs b/src/Shared/MSBuild.Testing/ProjectDirectory.cs
similarity index 97%
rename from src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/ProjectDirectory.cs
rename to src/Shared/MSBuild.Testing/ProjectDirectory.cs
index d0d0e571f4..0f669c31fc 100644
--- a/src/Razor/Microsoft.NET.Sdk.Razor/integrationtests/ProjectDirectory.cs
+++ b/src/Shared/MSBuild.Testing/ProjectDirectory.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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;
@@ -49,12 +49,11 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
}
var repositoryRoot = BuildVariables.RepoRoot;
- var solutionRoot = Path.Combine(repositoryRoot, "src", "Razor");
var binariesRoot = Path.GetDirectoryName(typeof(ProjectDirectory).Assembly.Location);
+ var testAppsRoot = BuildVariables.TestAppsRoot;
foreach (var project in new string[] { originalProjectName, }.Concat(additionalProjects))
{
- var testAppsRoot = Path.Combine(solutionRoot, "test", "testassets");
var projectRoot = Path.Combine(testAppsRoot, project);
if (!Directory.Exists(projectRoot))
{
@@ -146,6 +145,11 @@ $@"
.ForEach(file =>
{
var source = Path.Combine(testAppsRoot, file);
+ if (!File.Exists(source))
+ {
+ return;
+ }
+
var destination = Path.Combine(projectDestination, file);
File.Copy(source, destination);
});