From 9efb30aa1502576ea4f79d32b3c4ad445b610365 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Fri, 22 Nov 2024 16:28:09 -0800 Subject: [PATCH 1/2] Use WebAssemblyHotReloadCapabilities project property ... to determine WASM capabilities instead of querying the runtime, which might not be loaded yet. --- .../BlazorWebAssemblyDeltaApplier.cs | 132 ++++-------------- .../BlazorWebAssemblyHostedDeltaApplier.cs | 5 +- .../HotReload/CompilationHandler.cs | 9 +- .../Utilities/ProjectGraphNodeExtensions.cs | 4 + .../HotReload/ApplyDeltaTests.cs | 29 +++- 5 files changed, 68 insertions(+), 111 deletions(-) diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs index 701e9edb8254..2b7b8bf560a8 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs @@ -3,18 +3,26 @@ using System.Buffers; using System.Collections.Immutable; +using Microsoft.Build.Graph; using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; namespace Microsoft.DotNet.Watch { - internal sealed class BlazorWebAssemblyDeltaApplier(IReporter reporter, BrowserRefreshServer browserRefreshServer, Version? targetFrameworkVersion) : SingleProcessDeltaApplier(reporter) + internal sealed class BlazorWebAssemblyDeltaApplier(IReporter reporter, BrowserRefreshServer browserRefreshServer, ProjectGraphNode project) : SingleProcessDeltaApplier(reporter) { - private const string DefaultCapabilities60 = "Baseline"; - private const string DefaultCapabilities70 = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes"; - private const string DefaultCapabilities80 = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType"; + private static readonly ImmutableArray s_defaultCapabilities60 = + ["Baseline"]; + + private static readonly ImmutableArray s_defaultCapabilities70 = + ["Baseline", "AddMethodToExistingType", "AddStaticFieldToExistingType", "NewTypeDefinition", "ChangeCustomAttributes"]; + + private static readonly ImmutableArray s_defaultCapabilities80 = + ["Baseline", "AddMethodToExistingType", "AddStaticFieldToExistingType", "NewTypeDefinition", "ChangeCustomAttributes", + "AddInstanceFieldToExistingType", "GenericAddMethodToExistingType", "GenericUpdateMethod", "UpdateParameters", "GenericAddFieldToExistingType"]; + + private static readonly ImmutableArray s_defaultCapabilities90 = + s_defaultCapabilities80; - private ImmutableArray _cachedCapabilities; - private readonly SemaphoreSlim _capabilityRetrievalSemaphore = new(initialCount: 1); private int _updateId; public override void Dispose() @@ -31,109 +39,31 @@ public override async Task WaitForProcessRunningAsync(CancellationToken cancella // Alternatively, we could inject agent into blazor-devserver.dll and establish a connection on the named pipe. => await browserRefreshServer.WaitForClientConnectionAsync(cancellationToken); - public override async Task> GetApplyUpdateCapabilitiesAsync(CancellationToken cancellationToken) + public override Task> GetApplyUpdateCapabilitiesAsync(CancellationToken cancellationToken) { - var cachedCapabilities = _cachedCapabilities; - if (!cachedCapabilities.IsDefault) - { - return cachedCapabilities; - } + var capabilities = project.GetWebAssemblyCapabilities(); - await _capabilityRetrievalSemaphore.WaitAsync(cancellationToken); - try - { - if (_cachedCapabilities.IsDefault) - { - _cachedCapabilities = await RetrieveAsync(cancellationToken); - } - } - finally + if (capabilities.IsEmpty) { - _capabilityRetrievalSemaphore.Release(); - } + var targetFramework = project.GetTargetFrameworkVersion(); - return _cachedCapabilities; - - async Task> RetrieveAsync(CancellationToken cancellationToken) - { - var buffer = ArrayPool.Shared.Rent(32 * 1024); + Reporter.Verbose($"Using capabilities based on target framework: '{targetFramework}'."); - try + capabilities = targetFramework?.Major switch { - Reporter.Verbose("Connecting to the browser."); - - await browserRefreshServer.WaitForClientConnectionAsync(cancellationToken); - - string capabilities; - if (browserRefreshServer.Options.TestFlags.HasFlag(TestFlags.MockBrowser)) - { - // When testing return default capabilities without connecting to an actual browser. - capabilities = GetDefaultCapabilities(targetFrameworkVersion); - } - else - { - string? capabilityString = null; - - await browserRefreshServer.SendAndReceiveAsync( - request: _ => default(JsonGetApplyUpdateCapabilitiesRequest), - response: (value, reporter) => - { - var str = Encoding.UTF8.GetString(value); - if (str.StartsWith('!')) - { - reporter.Verbose($"Exception while reading WASM runtime capabilities: {str[1..]}"); - } - else if (str.Length == 0) - { - reporter.Verbose($"Unable to read WASM runtime capabilities"); - } - else if (capabilityString == null) - { - capabilityString = str; - } - else if (capabilityString != str) - { - reporter.Verbose($"Received different capabilities from different browsers:{Environment.NewLine}'{str}'{Environment.NewLine}'{capabilityString}'"); - } - }, - cancellationToken); - - if (capabilityString != null) - { - capabilities = capabilityString; - } - else - { - capabilities = GetDefaultCapabilities(targetFrameworkVersion); - Reporter.Verbose($"Falling back to default WASM capabilities: '{capabilities}'"); - } - } - - // Capabilities are expressed a space-separated string. - // e.g. https://github.com/dotnet/runtime/blob/14343bdc281102bf6fffa1ecdd920221d46761bc/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs#L87 - return capabilities.Split(' ').ToImmutableArray(); - } - catch (Exception e) when (!cancellationToken.IsCancellationRequested) - { - Reporter.Error($"Failed to read capabilities: {e.Message}"); - - // Do not attempt to retrieve capabilities again if it fails once, unless the operation is canceled. - return []; - } - finally - { - ArrayPool.Shared.Return(buffer); - } + 9 => s_defaultCapabilities90, + 8 => s_defaultCapabilities80, + 7 => s_defaultCapabilities70, + 6 => s_defaultCapabilities60, + _ => [], + }; + } + else + { + Reporter.Verbose($"Project specifies capabilities."); } - static string GetDefaultCapabilities(Version? targetFrameworkVersion) - => targetFrameworkVersion?.Major switch - { - >= 8 => DefaultCapabilities80, - >= 7 => DefaultCapabilities70, - >= 6 => DefaultCapabilities60, - _ => string.Empty, - }; + return Task.FromResult(capabilities); } public override async Task Apply(ImmutableArray updates, CancellationToken cancellationToken) diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedDeltaApplier.cs index 1eda8a6f7986..f2eb00de24bc 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedDeltaApplier.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedDeltaApplier.cs @@ -3,13 +3,14 @@ using System.Collections.Immutable; +using Microsoft.Build.Graph; using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; namespace Microsoft.DotNet.Watch { - internal sealed class BlazorWebAssemblyHostedDeltaApplier(IReporter reporter, BrowserRefreshServer browserRefreshServer, Version? targetFrameworkVersion) : DeltaApplier(reporter) + internal sealed class BlazorWebAssemblyHostedDeltaApplier(IReporter reporter, BrowserRefreshServer browserRefreshServer, ProjectGraphNode project) : DeltaApplier(reporter) { - private readonly BlazorWebAssemblyDeltaApplier _wasmApplier = new(reporter, browserRefreshServer, targetFrameworkVersion); + private readonly BlazorWebAssemblyDeltaApplier _wasmApplier = new(reporter, browserRefreshServer, project); private readonly DefaultDeltaApplier _hostApplier = new(reporter); public override void Dispose() diff --git a/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs index b64873f7a74d..47c54b8657d1 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs @@ -99,11 +99,11 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken) _reporter.Report(MessageDescriptor.HotReloadSessionStarted); } - private static DeltaApplier CreateDeltaApplier(HotReloadProfile profile, Version? targetFramework, BrowserRefreshServer? browserRefreshServer, IReporter processReporter) + private static DeltaApplier CreateDeltaApplier(HotReloadProfile profile, ProjectGraphNode project, BrowserRefreshServer? browserRefreshServer, IReporter processReporter) => profile switch { - HotReloadProfile.BlazorWebAssembly => new BlazorWebAssemblyDeltaApplier(processReporter, browserRefreshServer!, targetFramework), - HotReloadProfile.BlazorHosted => new BlazorWebAssemblyHostedDeltaApplier(processReporter, browserRefreshServer!, targetFramework), + HotReloadProfile.BlazorWebAssembly => new BlazorWebAssemblyDeltaApplier(processReporter, browserRefreshServer!, project), + HotReloadProfile.BlazorHosted => new BlazorWebAssemblyHostedDeltaApplier(processReporter, browserRefreshServer!, project), _ => new DefaultDeltaApplier(processReporter), }; @@ -121,8 +121,7 @@ private static DeltaApplier CreateDeltaApplier(HotReloadProfile profile, Version { var projectPath = projectNode.ProjectInstance.FullPath; - var targetFramework = projectNode.GetTargetFrameworkVersion(); - var deltaApplier = CreateDeltaApplier(profile, targetFramework, browserRefreshServer, processReporter); + var deltaApplier = CreateDeltaApplier(profile, projectNode, browserRefreshServer, processReporter); var processExitedSource = new CancellationTokenSource(); var processCommunicationCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(processExitedSource.Token, cancellationToken); diff --git a/src/BuiltInTools/dotnet-watch/Utilities/ProjectGraphNodeExtensions.cs b/src/BuiltInTools/dotnet-watch/Utilities/ProjectGraphNodeExtensions.cs index dc758ef74305..ae520eaff1ba 100644 --- a/src/BuiltInTools/dotnet-watch/Utilities/ProjectGraphNodeExtensions.cs +++ b/src/BuiltInTools/dotnet-watch/Utilities/ProjectGraphNodeExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Immutable; using Microsoft.Build.Graph; using Microsoft.DotNet.Cli; @@ -17,6 +18,9 @@ public static string GetTargetFramework(this ProjectGraphNode projectNode) public static Version? GetTargetFrameworkVersion(this ProjectGraphNode projectNode) => EnvironmentVariableNames.TryParseTargetFrameworkVersion(projectNode.ProjectInstance.GetPropertyValue("TargetFrameworkVersion")); + public static ImmutableArray GetWebAssemblyCapabilities(this ProjectGraphNode projectNode) + => [.. projectNode.ProjectInstance.GetPropertyValue("WebAssemblyHotReloadCapabilities").Split(';').Select(static c => c.Trim()).Where(static c => c != "")]; + public static bool IsTargetFrameworkVersionOrNewer(this ProjectGraphNode projectNode, Version minVersion) => GetTargetFrameworkVersion(projectNode) is { } version && version >= minVersion; diff --git a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs index a2060d3f9470..01c2eeae89d8 100644 --- a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs +++ b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs @@ -231,12 +231,25 @@ class AppUpdateHandler } } - [Fact] - public async Task BlazorWasm() + [Theory] + [CombinatorialData] + public async Task BlazorWasm(bool projectSpecifiesCapabilities) { - var testAsset = TestAssets.CopyTestAsset("WatchBlazorWasm") + var testAsset = TestAssets.CopyTestAsset("WatchBlazorWasm", identifier: projectSpecifiesCapabilities.ToString()) .WithSource(); + if (projectSpecifiesCapabilities) + { + testAsset = testAsset.WithProjectChanges(proj => + { + proj.Root.Descendants() + .First(e => e.Name.LocalName == "PropertyGroup") + .Add(XElement.Parse(""" + Baseline;AddMethodToExistingType + """)); + }); + } + var port = TestOptions.GetTestPort(); App.Start(testAsset, ["--urls", "http://localhost:" + port], testFlags: TestFlags.MockBrowser); @@ -256,6 +269,16 @@ public async Task BlazorWasm() UpdateSourceFile(Path.Combine(testAsset.Path, "Pages", "Index.razor"), newSource); await App.AssertOutputLineStartsWith(MessageDescriptor.HotReloadSucceeded, "blazorwasm (net9.0)"); + + // check project specified capapabilities: + if (projectSpecifiesCapabilities) + { + App.AssertOutputContains("dotnet watch 🔥 Hot reload capabilities: Baseline AddMethodToExistingType."); + } + else + { + App.AssertOutputContains("dotnet watch 🔥 Hot reload capabilities: Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType."); + } } [Fact] From ed733a88f7ecc5a285d9871127682f35f7ae1bbc Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 5 Feb 2025 16:38:44 -0800 Subject: [PATCH 2/2] 1 --- .../WebSocketScriptInjection.js | 112 +++++++++++------- .../dotnet-watch/Browser/BrowserConnector.cs | 3 +- .../Browser/BrowserRefreshServer.cs | 10 +- .../HotReload/CompilationHandler.cs | 2 + .../dotnet-watch/HotReload/ProjectLauncher.cs | 4 +- 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js b/src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js index e90cd86c7017..b53027963cd9 100644 --- a/src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js +++ b/src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js @@ -7,7 +7,30 @@ setTimeout(async function () { } window[scriptInjectedSentinel] = true; - // dotnet-watch browser reload script + const AgentMessageSeverity_Error = 2 + + var pendingUpdates = []; + var pendingUpdatesLog = []; + var initializedBlazor = undefined; + + window["__BlazorWebAssemblyInitializeForHotReload"] = async function (blazor) { + // apply pending updates + try { + pendingUpdates.forEach(update => pendingUpdatesLog.push(blazor.applyHotReloadDeltas(update.deltas, update.responseLoggingLevel))); + initializedBlazor = api; + updatesApplied = true; + } catch (error) { + console.error(error); + pendingUpdatesLog.push({ "message": getMessageAndStack(error), "severity": AgentMessageSeverity_Error }); + + // no updates can't be applied to this process: + pendingUpdates = undefined; + return; + } + + notifyHotReloadApplied(); + }; + // dotnet-watch browser reload script const webSocketUrls = '{{hostString}}'.split(','); const sharedSecret = await getSecret('{{ServerKey}}'); let connection; @@ -44,9 +67,18 @@ setTimeout(async function () { const payload = JSON.parse(message.data); const action = { 'UpdateStaticFile': () => updateStaticFile(payload.path), - 'BlazorHotReloadDeltav1': () => applyBlazorDeltas_legacy(payload.sharedSecret, payload.deltas, false), - 'BlazorHotReloadDeltav2': () => applyBlazorDeltas_legacy(payload.sharedSecret, payload.deltas, true), - 'BlazorHotReloadDeltav3': () => applyBlazorDeltas(payload.sharedSecret, payload.updateId, payload.deltas, payload.responseLoggingLevel), + 'BlazorHotReloadDeltav1': () => { + validateSecret(payload.sharedSecret); + applyBlazorDeltas_legacy(payload.deltas, false); + }, + 'BlazorHotReloadDeltav2': () => { + validateSecret(payload.sharedSecret); + applyBlazorDeltas_legacy(payload.deltas, true); + }, + 'BlazorHotReloadDeltav3': () => { + validateSecret(payload.sharedSecret); + applyBlazorDeltas(payload.deltas, payload.responseLoggingLevel); + }, 'HotReloadDiagnosticsv1': () => displayDiagnostics(payload.diagnostics), 'BlazorRequestApplyUpdateCapabilities': () => getBlazorWasmApplyUpdateCapabilities(false), 'BlazorRequestApplyUpdateCapabilities2': () => getBlazorWasmApplyUpdateCapabilities(true), @@ -137,13 +169,15 @@ setTimeout(async function () { styleElement.parentNode.insertBefore(newElement, styleElement.nextSibling); } - async function applyBlazorDeltas_legacy(serverSecret, deltas, sendErrorToClient) { + function validateSecret(serverSecret) { if (sharedSecret && (serverSecret != sharedSecret.encodedSharedSecret)) { // Validate the shared secret if it was specified. It might be unspecified in older versions of VS // that do not support this feature as yet. throw 'Unable to validate the server. Rejecting apply-update payload.'; } + } + async function applyBlazorDeltas_legacy(deltas, sendErrorToClient) { let applyError = undefined; if (window.Blazor?._internal?.applyHotReload) { // Only apply hot reload deltas if Blazor has been initialized. @@ -174,59 +208,49 @@ setTimeout(async function () { } } - async function applyBlazorDeltas(serverSecret, updateId, deltas, responseLoggingLevel) { - if (sharedSecret && (serverSecret != sharedSecret.encodedSharedSecret)) { - // Validate the shared secret if it was specified. It might be unspecified in older versions of VS - // that do not support this feature as yet. - throw 'Unable to validate the server. Rejecting apply-update payload.'; + async function applyBlazorDeltas(deltas, responseLoggingLevel) { + let updatesApplied = false + let success = true; + let log = []; + + if (pendingUpdatesLog.length > 0) { + log.push(pendingUpdatesLog); + pendingUpdatesLog = []; } - const AgentMessageSeverity_Error = 2 + let wasmDeltas = deltas.map(delta => { + return { + "moduleId": delta.moduleId, + "metadataDelta": delta.metadataDelta, + "ilDelta": delta.ilDelta, + "pdbDelta": delta.pdbDelta, + "updatedTypes": delta.updatedTypes, + }; + }); - let applyError = undefined; - let log = []; - if (window.Blazor?._internal?.applyHotReloadDeltas) { - // Only apply hot reload deltas if Blazor has been initialized. - // It's possible for Blazor to start after the initial page load, so we don't consider skipping this step - // to be a failure. These deltas will get applied later, when Blazor completes initialization. + if (initializedBlazor !== undefined) { try { - let wasmDeltas = deltas.map(delta => { - return { - "moduleId": delta.moduleId, - "metadataDelta": delta.metadataDelta, - "ilDelta": delta.ilDelta, - "pdbDelta": delta.pdbDelta, - "updatedTypes": delta.updatedTypes, - }; - }); - - log = window.Blazor._internal.applyHotReloadDeltas(wasmDeltas, responseLoggingLevel); + log.push(initializedBlazor.applyHotReloadDeltas(wasmDeltas, responseLoggingLevel)); + updatesApplied = true; } catch (error) { console.warn(error); - applyError = error; log.push({ "message": getMessageAndStack(error), "severity": AgentMessageSeverity_Error }); + success = false; } - } - - try { - let body = JSON.stringify({ - "id": updateId, - "deltas": deltas - }); - - await fetch('/_framework/blazor-hotreload', { method: 'post', headers: { 'content-type': 'application/json' }, body: body }); - } catch (error) { - console.warn(error); - applyError = error; - log.push({ "message": getMessageAndStack(error), "severity": AgentMessageSeverity_Error }); + } else if (pendingUpdates !== undefined) { + // Blazor is not initialized, defer delta application until it is: + pendingUpdates.push({ "deltas": wasmDeltas, "responseLoggingLevel": responseLoggingLevel }) + } else { + // pending updates failed to apply, we can't apply any more updates: + success = false; } connection.send(JSON.stringify({ - "success": !applyError, + "success": success, "log": log })); - if (!applyError) { + if (updatesApplied) { notifyHotReloadApplied(); } } diff --git a/src/BuiltInTools/dotnet-watch/Browser/BrowserConnector.cs b/src/BuiltInTools/dotnet-watch/Browser/BrowserConnector.cs index dc120099b3b7..6ecb16534f6d 100644 --- a/src/BuiltInTools/dotnet-watch/Browser/BrowserConnector.cs +++ b/src/BuiltInTools/dotnet-watch/Browser/BrowserConnector.cs @@ -49,6 +49,7 @@ await Task.WhenAll(serversToDispose.Select(async server => ProcessSpec processSpec, EnvironmentVariablesBuilder environmentBuilder, ProjectOptions projectOptions, + CompilationHandler compilationHandler, CancellationToken cancellationToken) { BrowserRefreshServer? server; @@ -59,7 +60,7 @@ await Task.WhenAll(serversToDispose.Select(async server => hasExistingServer = _servers.TryGetValue(projectNode, out server); if (!hasExistingServer) { - server = IsServerSupported(projectNode) ? new BrowserRefreshServer(context.EnvironmentOptions, context.Reporter) : null; + server = IsServerSupported(projectNode) ? new BrowserRefreshServer(compilationHandler, context.EnvironmentOptions, context.Reporter) : null; _servers.Add(projectNode, server); } } diff --git a/src/BuiltInTools/dotnet-watch/Browser/BrowserRefreshServer.cs b/src/BuiltInTools/dotnet-watch/Browser/BrowserRefreshServer.cs index 274a9c7dfcdf..0f4cba502848 100644 --- a/src/BuiltInTools/dotnet-watch/Browser/BrowserRefreshServer.cs +++ b/src/BuiltInTools/dotnet-watch/Browser/BrowserRefreshServer.cs @@ -28,8 +28,11 @@ internal sealed class BrowserRefreshServer : IAsyncDisposable private static readonly ReadOnlyMemory s_waitMessage = Encoding.UTF8.GetBytes("Wait"); private static readonly JsonSerializerOptions s_jsonSerializerOptions = new(JsonSerializerDefaults.Web); + public readonly EnvironmentOptions Options; + private readonly List _activeConnections = []; private readonly RSA _rsa; + private readonly CompilationHandler _compilationHandler; private readonly IReporter _reporter; private readonly TaskCompletionSource _terminateWebSocket; private readonly TaskCompletionSource _browserConnected; @@ -39,11 +42,10 @@ internal sealed class BrowserRefreshServer : IAsyncDisposable private IHost? _refreshServer; private string? _serverUrls; - public readonly EnvironmentOptions Options; - - public BrowserRefreshServer(EnvironmentOptions options, IReporter reporter) + public BrowserRefreshServer(CompilationHandler compilationHandler, EnvironmentOptions options, IReporter reporter) { _rsa = RSA.Create(2048); + _compilationHandler = compilationHandler; Options = options; _reporter = reporter; _terminateWebSocket = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -175,6 +177,8 @@ private async Task WebSocketRequestAsync(HttpContext context) _activeConnections.Add(connection); } + // TODO: send previous updates + _browserConnected.TrySetResult(); await _terminateWebSocket.Task; } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs index 47c54b8657d1..72e2df1f9b51 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs @@ -58,6 +58,8 @@ public void Dispose() Workspace?.Dispose(); } + public ImmutableList PreviousUpdates => _previousUpdates; + public async ValueTask TerminateNonRootProcessesAndDispose(CancellationToken cancellationToken) { _reporter.Verbose("Disposing remaining child processes."); diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs b/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs index 91d461a59a50..3dfa221e8e6f 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs @@ -101,7 +101,9 @@ public EnvironmentOptions EnvironmentOptions Reporter.Verbose($"Target process is '{targetPath}'"); } - var browserRefreshServer = await browserConnector.LaunchOrRefreshBrowserAsync(projectNode, processSpec, environmentBuilder, projectOptions, cancellationToken); + var browserRefreshServer = await browserConnector.LaunchOrRefreshBrowserAsync( + projectNode, processSpec, environmentBuilder, projectOptions, compilationHandler, cancellationToken); + environmentBuilder.ConfigureProcess(processSpec); var processReporter = new ProjectSpecificReporter(projectNode, Reporter);