Skip to content

Commit

Permalink
Refresh browser for all transitively referencing projects (#45095)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat authored Nov 26, 2024
1 parent 208df00 commit 2e06088
Show file tree
Hide file tree
Showing 20 changed files with 554 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ private async ValueTask DisplayResultsAsync(WatchHotReloadService.Updates update
switch (updates.Status)
{
case ModuleUpdateStatus.None:
_reporter.Output("No hot reload changes to apply.");
_reporter.Output("No C# changes to apply.");
break;

case ModuleUpdateStatus.Ready:
Expand Down
62 changes: 42 additions & 20 deletions src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.


using Microsoft.Build.Framework;
using Microsoft.Build.Graph;
using Microsoft.TemplateEngine.Utils;

namespace Microsoft.DotNet.Watch
{
Expand Down Expand Up @@ -48,41 +50,61 @@ public async ValueTask HandleFileChangesAsync(IReadOnlyList<ChangedFile> files,
}
}

var logger = reporter.IsVerbose ? new[] { new Build.Logging.ConsoleLogger() } : null;
if (!hasApplicableFiles)
{
return;
}

var logger = reporter.IsVerbose ? new[] { new Build.Logging.ConsoleLogger(LoggerVerbosity.Minimal) } : null;

var tasks = projectsToRefresh.Select(async projectNode =>
var buildTasks = projectsToRefresh.Select(projectNode => Task.Run(() =>
{
if (!projectNode.ProjectInstance.DeepCopy().Build(BuildTargetName, logger))
try
{
return false;
if (!projectNode.ProjectInstance.DeepCopy().Build(BuildTargetName, logger))
{
return null;
}
}

if (browserConnector.TryGetRefreshServer(projectNode, out var browserRefreshServer))
catch (Exception e)
{
await HandleBrowserRefresh(browserRefreshServer, projectNode.ProjectInstance.FullPath, cancellationToken);
reporter.Error($"[{projectNode.GetDisplayName()}] Target {BuildTargetName} failed to build: {e}");
return null;
}

return true;
});
return projectNode;
}));

var results = await Task.WhenAll(tasks).WaitAsync(cancellationToken);
var buildResults = await Task.WhenAll(buildTasks).WaitAsync(cancellationToken);

if (hasApplicableFiles)
var browserRefreshTasks = buildResults.Where(p => p != null)!.GetTransitivelyReferencingProjects().Select(async projectNode =>
{
var successfulCount = results.Sum(r => r ? 1 : 0);

if (successfulCount == results.Length)
{
reporter.Output("Hot reload of scoped css succeeded.", emoji: "🔥");
}
else if (successfulCount > 0)
if (browserConnector.TryGetRefreshServer(projectNode, out var browserRefreshServer))
{
reporter.Output($"Hot reload of scoped css partially succeeded: {successfulCount} project(s) out of {results.Length} were updated.", emoji: "🔥");
reporter.Verbose($"[{projectNode.GetDisplayName()}] Refreshing browser.");
await HandleBrowserRefresh(browserRefreshServer, projectNode.ProjectInstance.FullPath, cancellationToken);
}
else
{
reporter.Output("Hot reload of scoped css failed.", emoji: "🔥");
reporter.Verbose($"[{projectNode.GetDisplayName()}] No refresh server.");
}
});

await Task.WhenAll(browserRefreshTasks).WaitAsync(cancellationToken);

var successfulCount = buildResults.Sum(r => r != null ? 1 : 0);

if (successfulCount == buildResults.Length)
{
reporter.Output("Hot reload of scoped css succeeded.", emoji: "🔥");
}
else if (successfulCount > 0)
{
reporter.Output($"Hot reload of scoped css partially succeeded: {successfulCount} project(s) out of {buildResults.Length} were updated.", emoji: "🔥");
}
else
{
reporter.Output("Hot reload of scoped css failed.", emoji: "🔥");
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Build.Graph;

namespace Microsoft.DotNet.Watch
{
Expand All @@ -18,6 +19,8 @@ public async ValueTask<bool> HandleFileChangesAsync(IReadOnlyList<ChangedFile> f
{
var allFilesHandled = true;
var refreshRequests = new Dictionary<BrowserRefreshServer, List<string>>();
var projectsWithoutRefreshServer = new HashSet<ProjectGraphNode>();

for (int i = 0; i < files.Count; i++)
{
var file = files[i].Item;
Expand Down Expand Up @@ -45,11 +48,16 @@ public async ValueTask<bool> HandleFileChangesAsync(IReadOnlyList<ChangedFile> f
{
if (!refreshRequests.TryGetValue(refreshServer, out var filesPerServer))
{
reporter.Verbose($"[{projectNode.GetDisplayName()}] Refreshing browser.");
refreshRequests.Add(refreshServer, filesPerServer = []);
}

filesPerServer.Add(file.StaticWebAssetPath);
}
else if (projectsWithoutRefreshServer.Add(projectNode))
{
reporter.Verbose($"[{projectNode.GetDisplayName()}] No refresh server.");
}
}
}
}
Expand All @@ -64,7 +72,7 @@ public async ValueTask<bool> HandleFileChangesAsync(IReadOnlyList<ChangedFile> f
// Serialize all requests sent to a single server:
foreach (var path in request.Value)
{
reporter.Verbose($"Sending static file update request for asset '{path}'");
reporter.Verbose($"Sending static file update request for asset '{path}'.");
var message = JsonSerializer.SerializeToUtf8Bytes(new UpdateStaticFileMessage { Path = path }, s_jsonSerializerOptions);
await request.Key.SendAsync(message, cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,28 @@ public static bool IsNetCoreApp(this ProjectGraphNode projectNode, Version minVe

public static IEnumerable<string> GetCapabilities(this ProjectGraphNode projectNode)
=> projectNode.ProjectInstance.GetItems("ProjectCapability").Select(item => item.EvaluatedInclude);

public static IEnumerable<ProjectGraphNode> GetTransitivelyReferencingProjects(this IEnumerable<ProjectGraphNode> projects)
{
var visited = new HashSet<ProjectGraphNode>();
var queue = new Queue<ProjectGraphNode>();
foreach (var project in projects)
{
queue.Enqueue(project);
}

while (queue.Count > 0)
{
var project = queue.Dequeue();
if (visited.Add(project))
{
foreach (var referencingProject in project.ReferencingProjects)
{
queue.Enqueue(referencingProject);
}
}
}

return visited;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>

<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]"/>
<link rel="stylesheet" href="@Assets["app.css"]"/>
<link rel="stylesheet" href="@Assets["RazorApp.styles.css"]"/>
<ImportMap/>

@*
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css"/>
<link rel="stylesheet" href="RazorApp.styles.css"/>
*@

<link rel="icon" type="image/png" href="favicon.png"/>
<HeadOutlet @rendermode="InteractiveServer"/>
</head>

<body>
<Routes @rendermode="InteractiveServer"/>
<script src="_framework/blazor.web.js"></script>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<NavMenu/>
</div>

<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>

<article class="content px-4">
@Body
</article>
</main>
</div>

<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}

main {
flex: 1;
}

.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}

.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}

.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}

.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}

@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}

.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}

@media (min-width: 641px) {
.page {
flex-direction: row;
}

.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}

.top-row {
position: sticky;
top: 0;
z-index: 1;
}

.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}

.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">RazorApp</a>
</div>
</div>

<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="nav flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
</nav>
</div>
Loading

0 comments on commit 2e06088

Please sign in to comment.