Skip to content

Commit fe3a42b

Browse files
authored
Add support for hot reload to hosted BlazorWasm apps (#16701)
* Fix a bug where deltas where being sent in the wrong format to BlazorWASM * Allow recovering from compilation errors in a BlazorWASM app. Fixes dotnet/aspnetcore#30815
1 parent 2a788df commit fe3a42b

File tree

5 files changed

+84
-9
lines changed

5 files changed

+84
-9
lines changed

src/BuiltInTools/BrowserRefresh/WebSocketScriptInjection.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ setTimeout(function () {
9999

100100
function applyBlazorDeltas(deltas) {
101101
deltas.forEach(d => window.Blazor._internal.applyHotReload(d.moduleId, d.metadataDelta, d.ilDelta));
102+
notifyHotReloadApplied();
102103
}
103104

104105
function displayDiagnostics(diagnostics) {

src/BuiltInTools/dotnet-watch/HotReload/AspNetCoreDeltaApplier.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public AspNetCoreDeltaApplier(IReporter reporter)
2626
_reporter = reporter;
2727
}
2828

29+
public bool SuppressBrowserRefreshAfterApply { get; init; }
30+
2931
public async ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken cancellationToken)
3032
{
3133
if (_pipe is not null)
@@ -104,7 +106,7 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, string changedFil
104106
return false;
105107
}
106108

107-
if (context.BrowserRefreshServer != null)
109+
if (!SuppressBrowserRefreshAfterApply && context.BrowserRefreshServer is not null)
108110
{
109111
if (result == ApplyResult.Success_RefreshBrowser)
110112
{

src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, string changedFil
3939
Deltas = solutionUpdate.Select(c => new UpdateDelta
4040
{
4141
ModuleId = c.ModuleId,
42-
MetadataDelta = c.MetadataDelta,
43-
ILDelta = c.ILDelta,
42+
MetadataDelta = c.MetadataDelta.ToArray(),
43+
ILDelta = c.ILDelta.ToArray(),
4444
}),
4545
};
4646

@@ -49,7 +49,18 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, string changedFil
4949
return true;
5050
}
5151

52-
public ValueTask ReportDiagnosticsAsync(DotNetWatchContext context, IEnumerable<string> diagnostics, CancellationToken cancellationToken) => throw new NotImplementedException();
52+
public async ValueTask ReportDiagnosticsAsync(DotNetWatchContext context, IEnumerable<string> diagnostics, CancellationToken cancellationToken)
53+
{
54+
if (context.BrowserRefreshServer != null)
55+
{
56+
var message = new HotReloadDiagnostics
57+
{
58+
Diagnostics = diagnostics
59+
};
60+
61+
await context.BrowserRefreshServer.SendJsonSerlialized(message, cancellationToken);
62+
}
63+
}
5364

5465
public void Dispose()
5566
{
@@ -65,8 +76,15 @@ private readonly struct UpdatePayload
6576
private readonly struct UpdateDelta
6677
{
6778
public Guid ModuleId { get; init; }
68-
public ImmutableArray<byte> MetadataDelta { get; init; }
69-
public ImmutableArray<byte> ILDelta { get; init; }
79+
public byte[] MetadataDelta { get; init; }
80+
public byte[] ILDelta { get; init; }
81+
}
82+
83+
public readonly struct HotReloadDiagnostics
84+
{
85+
public string Type => "HotReloadDiagnosticsv1";
86+
87+
public IEnumerable<string> Diagnostics { get; init; }
7088
}
7189
}
7290
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.Collections.Immutable;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api;
9+
using Microsoft.Extensions.Tools.Internal;
10+
11+
namespace Microsoft.DotNet.Watcher.Tools
12+
{
13+
internal class BlazorWebAssemblyHostedDeltaApplier : IDeltaApplier
14+
{
15+
private readonly BlazorWebAssemblyDeltaApplier _wasmApplier;
16+
private readonly AspNetCoreDeltaApplier _hostApplier;
17+
18+
public BlazorWebAssemblyHostedDeltaApplier(IReporter reporter)
19+
{
20+
_wasmApplier = new BlazorWebAssemblyDeltaApplier(reporter);
21+
_hostApplier = new AspNetCoreDeltaApplier(reporter)
22+
{
23+
SuppressBrowserRefreshAfterApply = true,
24+
};
25+
}
26+
27+
public async ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken cancellationToken)
28+
{
29+
await _wasmApplier.InitializeAsync(context, cancellationToken);
30+
await _hostApplier.InitializeAsync(context, cancellationToken);
31+
}
32+
33+
public async ValueTask<bool> Apply(DotNetWatchContext context, string changedFile, ImmutableArray<WatchHotReloadService.Update> solutionUpdate, CancellationToken cancellationToken)
34+
{
35+
return await _hostApplier.Apply(context, changedFile, solutionUpdate, cancellationToken) &&
36+
await _wasmApplier.Apply(context, changedFile, solutionUpdate, cancellationToken);
37+
}
38+
39+
public async ValueTask ReportDiagnosticsAsync(DotNetWatchContext context, IEnumerable<string> diagnostics, CancellationToken cancellationToken)
40+
{
41+
// Both WASM and Host have similar implementations for diagnostics. We could pick either to report diagnostics.
42+
await _hostApplier.ReportDiagnosticsAsync(context, diagnostics, cancellationToken);
43+
}
44+
45+
public void Dispose()
46+
{
47+
_hostApplier.Dispose();
48+
_wasmApplier.Dispose();
49+
}
50+
}
51+
}

src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ public async ValueTask InitializeAsync(DotNetWatchContext context, CancellationT
3737
{
3838
if (_deltaApplier is null)
3939
{
40-
_deltaApplier = context.DefaultLaunchSettingsProfile.HotReloadProfile == "blazorwasm" ?
41-
new BlazorWebAssemblyDeltaApplier(_reporter) :
42-
new AspNetCoreDeltaApplier(_reporter);
40+
_deltaApplier = context.DefaultLaunchSettingsProfile.HotReloadProfile switch
41+
{
42+
"blazorwasm" => new BlazorWebAssemblyDeltaApplier(_reporter),
43+
"blazorwasmhosted" => new BlazorWebAssemblyHostedDeltaApplier(_reporter),
44+
_ => new AspNetCoreDeltaApplier(_reporter),
45+
};
4346
}
4447

4548
await _deltaApplier.InitializeAsync(context, cancellationToken);

0 commit comments

Comments
 (0)