Skip to content

[browser] Integrate DevServer into WasmAppHost #88985

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 29 commits into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
54d2152
Copy dev server from blazor
maraf Jul 11, 2023
55cac05
Add target path
maraf Jul 11, 2023
b9697d9
Merge remote-tracking branch 'upstream/main' into WasmBrowserHost
maraf Jul 14, 2023
af5bfd3
Add debugging. Copy pattern from WebServer
maraf Jul 14, 2023
9901130
Use DevServer when SWA manifest exists
maraf Jul 17, 2023
0c64fb7
Target path is not needed
maraf Jul 17, 2023
8e7d010
Namespace for DevServer classes
maraf Jul 18, 2023
819bd54
Make HTMLPath optional
maraf Jul 18, 2023
dd8531e
Merge remote-tracking branch 'upstream/main' into WasmBrowserHost
maraf Jul 18, 2023
5e2ea4b
Runtimeconfig template
maraf Jul 18, 2023
7ae7a4b
WIP
maraf Jul 18, 2023
d4b55a2
Merge remote-tracking branch 'upstream/main' into WasmBrowserHost
maraf Jul 19, 2023
56bc2e3
DevServer without MainAssembly by scanning for swa manifest in file s…
maraf Jul 21, 2023
df4d5fa
Set run command and arguments in WasmSDK
maraf Jul 21, 2023
38019bb
Include WasmAppHost in Wasm SDK pack
maraf Jul 21, 2023
404797c
Use dotnet run in TestAppScenarios
maraf Jul 21, 2023
b66ae38
Workaround for GenerateRuntimeConfigurationFiles
maraf Jul 21, 2023
3d6a3ed
Feedback
maraf Jul 24, 2023
09212f1
Load BrowserDebugHost from "current folder"
maraf Jul 24, 2023
29f442c
Feedback from @radical
maraf Jul 25, 2023
d0d85cf
Merge remote-tracking branch 'upstream/main' into WasmBrowserHost
maraf Jul 26, 2023
ec957e4
Merge remote-tracking branch 'upstream/main' into WasmBrowserHost
maraf Jul 27, 2023
ea68a69
Use separate argument to pick devserver
maraf Jul 27, 2023
d142f65
Add message about debugger
maraf Jul 27, 2023
4694490
Remove ForPublish from tests
maraf Jul 28, 2023
0923728
Remove GenerateRuntimeConfigurationFiles=false from SDK and GenerateR…
maraf Jul 28, 2023
ea8f894
Unify message for debugger on https
maraf Jul 28, 2023
010cb1f
Merge remote-tracking branch 'upstream/main' into WasmBrowserHost
maraf Jul 28, 2023
659adae
UseStaticWebAssets should default to false
maraf Jul 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@

<ItemGroup>
<ProjectReference Include="$(RepoTasksDir)Microsoft.NET.Sdk.WebAssembly.Pack.Tasks\Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj" />
<ProjectReference Include="$(RepoRoot)src\mono\wasm\host\WasmAppHost.csproj" />
<PackageFile Include="build\*.props;build\*.targets;build\*.web.config" TargetPath="build" />
</ItemGroup>

<Target Name="_PrepareForPack" BeforeTargets="GetPackageFiles">
<ItemGroup>
<PackageFile Include="$(SdkTargetsPath)" TargetPath="Sdk" />

<_WasmAppHostFiles Include="$(WasmAppHostDir)\*" TargetPath="WasmAppHost" />
<PackageFile Include="@(_WasmAppHostFiles)" />
</ItemGroup>

<Error Text="Could not find WasmAppHost files in $(WasmAppHostDir)" Condition="@(_WasmAppHostFiles->Count()) == 0" />
</Target>

<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ Copyright (c) .NET Foundation. All rights reserved.
-->
<Project ToolsVersion="14.0">

<PropertyGroup>
<_UseBlazorDevServer>$(RunArguments.Contains('blazor-devserver.dll').ToString().ToLower())</_UseBlazorDevServer>
</PropertyGroup>
<PropertyGroup Condition="'$(_WebAssemblyUserRunParameters)' == '' and '$(_UseBlazorDevServer)' == 'false'">
<RunCommand Condition="'$(DOTNET_HOST_PATH)' != '' and Exists($(DOTNET_HOST_PATH))">$(DOTNET_HOST_PATH)</RunCommand>
<RunCommand Condition="'$(RunCommand)' == ''">dotnet</RunCommand>

<WasmAppHostDir>$([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory), '..', 'WasmAppHost'))</WasmAppHostDir>
<_RuntimeConfigJsonPath>$([MSBuild]::NormalizePath($(OutputPath), '$(AssemblyName).runtimeconfig.json'))</_RuntimeConfigJsonPath>
<RunArguments>exec &quot;$([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll'))&quot; --use-staticwebassets --runtime-config &quot;$(_RuntimeConfigJsonPath)&quot; $(WasmHostArguments)</RunArguments>
<RunWorkingDirectory>$(OutputPath)</RunWorkingDirectory>
</PropertyGroup>

<PropertyGroup>
<EnableDefaultContentItems Condition=" '$(EnableDefaultContentItems)' == '' ">true</EnableDefaultContentItems>

Expand Down Expand Up @@ -61,7 +74,6 @@ Copyright (c) .NET Foundation. All rights reserved.

<!-- Turn off parts of the build that do not apply to WASM projects -->
<GenerateDependencyFile>false</GenerateDependencyFile>
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
<PreserveCompilationContext>false</PreserveCompilationContext>
<PreserveCompilationReferences>false</PreserveCompilationReferences>
<IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicatio

var result = await RunSdkStyleApp(new(
Configuration: "Debug",
ForPublish: true,
TestScenario: "AppSettingsTest",
BrowserQueryString: new Dictionary<string, string> { ["applicationEnvironment"] = applicationEnvironment }
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ protected string GetBinLogFilePath(string suffix = null)

protected async Task<RunResult> RunSdkStyleApp(RunOptions options)
{
string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files";
string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(options.Configuration, forPublish: options.ForPublish), ".."));
string runArgs = $"run -c {options.Configuration}";
string workingDirectory = _projectDir;

using var runCommand = new RunCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(workingDirectory);
Expand Down Expand Up @@ -123,7 +123,6 @@ protected record RunOptions(
string Configuration,
string TestScenario,
Dictionary<string, string> BrowserQueryString = null,
bool ForPublish = false,
Action<IConsoleMessage, IPage> OnConsoleMessage = null,
int? ExpectedExitCode = 0
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public async Task DownloadProgressFinishes(bool failAssemblyDownload)

var result = await RunSdkStyleApp(new(
Configuration: "Debug",
ForPublish: true,
TestScenario: "DownloadResourceProgressTest",
BrowserQueryString: new Dictionary<string, string> { ["failAssemblyDownload"] = failAssemblyDownload.ToString().ToLowerInvariant() }
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task LoadLazyAssemblyBeforeItIsNeeded()
CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests");
PublishProject("Debug");

var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LazyLoadingTest"));
var result = await RunSdkStyleApp(new(Configuration: "Debug", TestScenario: "LazyLoadingTest"));
Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON");
}

Expand All @@ -38,7 +38,6 @@ public async Task FailOnMissingLazyAssembly()

var result = await RunSdkStyleApp(new(
Configuration: "Debug",
ForPublish: true,
TestScenario: "LazyLoadingTest",
BrowserQueryString: new Dictionary<string, string> { ["loadRequiredAssembly"] = "false" },
ExpectedExitCode: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task LoadLibraryInitializer()
CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_LoadLibraryInitializer");
PublishProject("Debug");

var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest"));
var result = await RunSdkStyleApp(new(Configuration: "Debug", TestScenario: "LibraryInitializerTest"));
Assert.Collection(
result.TestOutput,
m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m)
Expand All @@ -44,7 +44,6 @@ public async Task AbortStartupOnError()

var result = await RunSdkStyleApp(new(
Configuration: "Debug",
ForPublish: true,
TestScenario: "LibraryInitializerTest",
BrowserQueryString: new Dictionary<string, string> { ["throwError"] = "true" },
ExpectedExitCode: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFi
public async Task LoadSatelliteAssembly()
{
CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests");
PublishProject("Debug");

var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "SatelliteAssembliesTest"));
var result = await RunSdkStyleApp(new(Configuration: "Debug", TestScenario: "SatelliteAssembliesTest"));
Assert.Collection(
result.TestOutput,
m => Assert.Equal("default: hello", m),
Expand Down
3 changes: 2 additions & 1 deletion src/mono/wasm/host/BrowserArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable enable

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Mono.Options;

Expand Down Expand Up @@ -37,8 +38,8 @@ public void ParseJsonProperties(IDictionary<string, JsonElement>? properties)
ForwardConsoleOutput = forwardConsoleElement.GetBoolean();
}

[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Needs to validate instance members")]
public void Validate()
{
CommonConfiguration.CheckPathOrInAppPath(CommonConfig.AppPath, HTMLPath, "html-path");
}
}
134 changes: 107 additions & 27 deletions src/mono/wasm/host/BrowserHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.WebAssembly.AppHost.DevServer;
using Microsoft.WebAssembly.Diagnostics;

#nullable enable
Expand Down Expand Up @@ -44,7 +47,7 @@ public static async Task<int> InvokeAsync(CommonConfiguration commonArgs,

private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken token)
{
if (_args.CommonConfig.Debugging)
if (_args.CommonConfig.Debugging && !_args.CommonConfig.UseStaticWebAssets)
{
ProxyOptions options = _args.CommonConfig.ToProxyOptions();
_ = Task.Run(() => DebugProxyHost.RunDebugProxyAsync(options, Array.Empty<string>(), loggerFactory, token), token)
Expand Down Expand Up @@ -75,8 +78,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
? aspnetUrls.Split(';', StringSplitOptions.RemoveEmptyEntries)
: new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" };

(ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath,
_args.ForwardConsoleOutput ?? false,
(ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args,
urls,
token);

Expand All @@ -85,32 +87,104 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
foreach (string url in fullUrls)
Console.WriteLine($"App url: {url}");

if (serverURLs.DebugPath != null)
{
Console.WriteLine($"Debug at url: {BuildUrl(serverURLs.Http, serverURLs.DebugPath, string.Empty)}");

if (serverURLs.Https != null)
Console.WriteLine($"Debug at url: {BuildUrl(serverURLs.Https, serverURLs.DebugPath, string.Empty)}");
}

await host.WaitForShutdownAsync(token);
}

private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token)
private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(BrowserArguments args, string[] urls, CancellationToken token)
{
WasmTestMessagesProcessor? logProcessor = null;
if (forwardConsole)
Func<WebSocket, Task>? onConsoleConnected = null;
if (args.ForwardConsoleOutput ?? false)
{
logProcessor = new(_logger);
WasmTestMessagesProcessor logProcessor = new(_logger);
onConsoleConnected = socket => RunConsoleMessagesPump(socket, logProcessor!, token);
}

WebServerOptions options = new
(
OnConsoleConnected: forwardConsole
? socket => RunConsoleMessagesPump(socket, logProcessor!, token)
: null,
ContentRootPath: Path.GetFullPath(appPath),
WebServerUseCors: true,
WebServerUseCrossOriginPolicy: true,
Urls: urls
);

(ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token);
return (serverURLs, host);
// If we are using new browser template, use dev server
if (args.CommonConfig.UseStaticWebAssets)
{
DevServerOptions devServerOptions = CreateDevServerOptions(args, urls, onConsoleConnected);
return await DevServer.DevServer.StartAsync(devServerOptions, _logger, token);
}

// Otherwise for old template, use web server
WebServerOptions webServerOptions = CreateWebServerOptions(urls, args.CommonConfig.AppPath, onConsoleConnected);
return await WebServer.StartAsync(webServerOptions, _logger, token);
}

private static WebServerOptions CreateWebServerOptions(string[] urls, string appPath, Func<WebSocket, Task>? onConsoleConnected) => new
(
OnConsoleConnected: onConsoleConnected,
ContentRootPath: Path.GetFullPath(appPath),
WebServerUseCors: true,
WebServerUseCrossOriginPolicy: true,
Urls: urls
);

private static DevServerOptions CreateDevServerOptions(BrowserArguments args, string[] urls, Func<WebSocket, Task>? onConsoleConnected)
{
const string staticWebAssetsV1Extension = ".StaticWebAssets.xml";
const string staticWebAssetsV2Extension = ".staticwebassets.runtime.json";

DevServerOptions? devServerOptions = null;

string appPath = args.CommonConfig.AppPath;
if (args.CommonConfig.HostProperties.MainAssembly != null)
{
// If we have main assembly name, try to find static web assets manifest by precise name.

var mainAssemblyPath = Path.Combine(appPath, args.CommonConfig.HostProperties.MainAssembly);
var staticWebAssetsPath = Path.ChangeExtension(mainAssemblyPath, staticWebAssetsV2Extension);
if (File.Exists(staticWebAssetsPath))
{
devServerOptions = CreateDevServerOptions(urls, staticWebAssetsPath, onConsoleConnected);
}
else
{
staticWebAssetsPath = Path.ChangeExtension(mainAssemblyPath, staticWebAssetsV1Extension);
if (File.Exists(staticWebAssetsPath))
devServerOptions = CreateDevServerOptions(urls, staticWebAssetsPath, onConsoleConnected);
}

if (devServerOptions == null)
devServerOptions = CreateDevServerOptions(urls, mainAssemblyPath, onConsoleConnected);
}
else
{
// If we don't have main assembly name, try to find static web assets manifest by search in the directory.

var staticWebAssetsPath = FindFirstFileWithExtension(appPath, staticWebAssetsV2Extension)
?? FindFirstFileWithExtension(appPath, staticWebAssetsV1Extension);

if (staticWebAssetsPath != null)
devServerOptions = CreateDevServerOptions(urls, staticWebAssetsPath, onConsoleConnected);

if (devServerOptions == null)
throw new CommandLineException("Please, provide mainAssembly in hostProperties of runtimeconfig");
}

return devServerOptions;
}

private static DevServerOptions CreateDevServerOptions(string[] urls, string staticWebAssetsPath, Func<WebSocket, Task>? onConsoleConnected) => new
(
OnConsoleConnected: onConsoleConnected,
StaticWebAssetsPath: staticWebAssetsPath,
WebServerUseCors: true,
WebServerUseCrossOriginPolicy: true,
Urls: urls
);

private static string? FindFirstFileWithExtension(string directory, string extension)
=> Directory.EnumerateFiles(directory, "*" + extension).First();

private async Task RunConsoleMessagesPump(WebSocket socket, WasmTestMessagesProcessor messagesProcessor, CancellationToken token)
{
byte[] buff = new byte[4000];
Expand Down Expand Up @@ -169,7 +243,7 @@ private string[] BuildUrls(ServerURLs serverURLs, IEnumerable<string> passThroug
}

string query = sb.ToString();
string filename = Path.GetFileName(_args.HTMLPath!);
string? filename = _args.HTMLPath != null ? Path.GetFileName(_args.HTMLPath) : null;
string httpUrl = BuildUrl(serverURLs.Http, filename, query);

return string.IsNullOrEmpty(serverURLs.Https)
Expand All @@ -179,12 +253,18 @@ private string[] BuildUrls(ServerURLs serverURLs, IEnumerable<string> passThroug
httpUrl,
BuildUrl(serverURLs.Https!, filename, query)
});
}

static string BuildUrl(string baseUrl, string htmlFileName, string query)
=> new UriBuilder(baseUrl)
{
Query = query,
Path = htmlFileName
}.ToString();
private static string BuildUrl(string baseUrl, string? htmlFileName, string query)
{
var uriBuilder = new UriBuilder(baseUrl)
{
Query = query
};

if (htmlFileName != null)
uriBuilder.Path = htmlFileName;

return uriBuilder.ToString();
}
}
Loading