Skip to content

Commit

Permalink
[browser] Integrate DevServer into WasmAppHost (#88985)
Browse files Browse the repository at this point in the history
  • Loading branch information
maraf committed Jul 29, 2023
1 parent bee48e8 commit d5c4a4e
Show file tree
Hide file tree
Showing 21 changed files with 1,401 additions and 89 deletions.
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

0 comments on commit d5c4a4e

Please sign in to comment.