Trigger live reload feature after successful builds in VS

This commit is contained in:
Steve Sanderson 2018-04-04 11:29:59 +01:00
parent 5dfa857eab
commit 71a3e31799
4 changed files with 137 additions and 4 deletions

View File

@ -59,10 +59,16 @@
-->
<Target Name="_BlazorIssueLiveReloadNotification"
AfterTargets="Build"
Condition="'$(UseBlazorLiveReloading)'=='true' AND '$(_BlazorDidCopyFilesToOutputDirectory)'=='true' AND '$(BuildingInsideVisualStudio)'!='true'">
<!-- Touch the signal file to trigger a reload -->
<WriteLinesToFile File="$(ProjectDir)$(OutputPath)$(BlazorBuildCompletedSignalPath)" Lines="_" />
<Delete Files="$(ProjectDir)$(OutputPath)$(BlazorBuildCompletedSignalPath)" />
Condition="'$(UseBlazorLiveReloading)'=='true' AND '$(_BlazorDidCopyFilesToOutputDirectory)'=='true'">
<PropertyGroup>
<_BlazorBuildCompletedSignalFullPath>$(ProjectDir)$(OutputPath)$(BlazorBuildCompletedSignalPath)</_BlazorBuildCompletedSignalFullPath>
</PropertyGroup>
<!--
If this is a command-line build, touch the signal file to trigger a reload.
If it's a VS build, then the VS extension will write the signal file instead.
-->
<WriteLinesToFile Condition="'$(BuildingInsideVisualStudio)'!='true'" File="$(_BlazorBuildCompletedSignalFullPath)" Lines="_" />
<Delete Condition="'$(BuildingInsideVisualStudio)'!='true'" Files="$(_BlazorBuildCompletedSignalFullPath)" />
</Target>
<!-- Preparing blazor files for output:

View File

@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.BlazorExtension
{
@ -11,8 +12,24 @@ namespace Microsoft.VisualStudio.BlazorExtension
[PackageRegistration(UseManagedResourcesOnly = true)]
[AboutDialogInfo(PackageGuidString, "ASP.NET Core Blazor Language Services", "#110", "112")]
[Guid(BlazorPackage.PackageGuidString)]
[ProvideAutoLoad(UIContextGuids80.SolutionExists)]
public sealed class BlazorPackage : Package
{
public const string PackageGuidString = "d9fe04bc-57a7-4107-915e-3a5c2f9e19fb";
protected override void Initialize()
{
base.Initialize();
RegisterLiveReloadBuildWatcher();
}
private void RegisterLiveReloadBuildWatcher()
{
// No need to unadvise, as this only happens once anyway
ThreadHelper.ThrowIfNotOnUIThread();
var buildManager = (IVsSolutionBuildManager)GetService(typeof(SVsSolutionBuildManager));
var hr = buildManager.AdviseUpdateSolutionEvents(new LiveReloadBuildWatcher(), out var cookie);
Marshal.ThrowExceptionForHR(hr);
}
}
}

View File

@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.VisualStudio.ProjectSystem.Properties;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.VisualStudio.BlazorExtension
{
internal class LiveReloadBuildWatcher : IVsUpdateSolutionEvents2
{
const string BlazorProjectCapability = "Blazor";
private bool _isListeningForProjectBuilds = false;
private List<string> _signalFilePathsToNotify = new List<string>();
public int UpdateSolution_Begin(ref int pfCancelUpdate)
{
_signalFilePathsToNotify.Clear();
_isListeningForProjectBuilds = true;
return VSConstants.S_OK;
}
public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand)
{
_isListeningForProjectBuilds = false;
foreach (var fullPath in _signalFilePathsToNotify)
{
try
{
File.WriteAllText(fullPath, string.Empty);
File.Delete(fullPath);
}
catch (Exception ex)
{
AttemptLogError($"Blazor live reloading was unable to write to the signal " +
$"file at {fullPath}. To disable live reloading, set the property " +
$"'UseBlazorLiveReloading' to 'false' in your project file." +
$"\nThe exception was: {ex.Message}\n{ex.StackTrace}");
}
}
return VSConstants.S_OK;
}
private void AttemptLogError(string message)
{
ThreadHelper.ThrowIfNotOnUIThread();
var outputWindow = (IVsOutputWindow)Package.GetGlobalService(typeof(SVsOutputWindow));
if (outputWindow != null)
{
outputWindow.GetPane(VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid, out var pane);
if (pane != null)
{
pane.OutputString(message);
pane.Activate();
}
}
}
public int UpdateSolution_StartUpdate(ref int pfCancelUpdate)
=> VSConstants.S_OK;
public int UpdateSolution_Cancel()
=> VSConstants.S_OK;
public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
=> VSConstants.S_OK;
public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel)
=> VSConstants.S_OK;
public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel)
{
if (_isListeningForProjectBuilds
&& fSuccess == 1 // i.e., build succeeded
&& pHierProj.IsCapabilityMatch(BlazorProjectCapability))
{
var configuredProject = ((IVsBrowseObjectContext)pCfgProj).ConfiguredProject;
var projectLockService = configuredProject
.UnconfiguredProject
.ProjectService
.Services
.ProjectLockService;
ThreadHelper.JoinableTaskFactory.Run(async delegate
{
using (var access = await projectLockService.ReadLockAsync())
{
var project = await access.GetProjectAsync(configuredProject);
// Now we can evaluate MSBuild properties
var signalFileFullPath = project.GetPropertyValue("_BlazorBuildCompletedSignalFullPath");
if (!string.IsNullOrEmpty(signalFileFullPath))
{
_signalFilePathsToNotify.Add(signalFileFullPath);
}
}
});
}
return VSConstants.S_OK;
}
}
}

View File

@ -275,6 +275,7 @@
<ItemGroup>
<Compile Include="AboutDialogInfoAttribute.cs" />
<Compile Include="BlazorPackage.cs" />
<Compile Include="LiveReloadBuildWatcher.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>