Revert live reloading implementation. See PR for explanation.

This commit is contained in:
Steve Sanderson 2018-04-04 17:45:56 +01:00
parent 707e781e5d
commit d3dc294d5e
25 changed files with 10 additions and 715 deletions

View File

@ -90,8 +90,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestContentPackage", "test\testapps\TestContentPackage\TestContentPackage.csproj", "{C57382BC-EE93-49D5-BC40-5C98AF8AA048}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveReloadTestApp", "test\testapps\LiveReloadTestApp\LiveReloadTestApp.csproj", "{0246AA77-1A27-4A67-874B-6EF6F99E414E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{36A7DEB7-5F88-4BFB-B57E-79EEC9950E25}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Performance", "benchmarks\Microsoft.AspNetCore.Blazor.Performance\Microsoft.AspNetCore.Blazor.Performance.csproj", "{50F6820F-D058-4E68-9E15-801F893F514E}"
@ -319,14 +317,6 @@ Global
{C57382BC-EE93-49D5-BC40-5C98AF8AA048}.Release|Any CPU.Build.0 = Release|Any CPU
{C57382BC-EE93-49D5-BC40-5C98AF8AA048}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{C57382BC-EE93-49D5-BC40-5C98AF8AA048}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.Release|Any CPU.Build.0 = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{0246AA77-1A27-4A67-874B-6EF6F99E414E}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{50F6820F-D058-4E68-9E15-801F893F514E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50F6820F-D058-4E68-9E15-801F893F514E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50F6820F-D058-4E68-9E15-801F893F514E}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
@ -375,7 +365,6 @@ Global
{43E39257-7DC1-46BD-9BD9-2319A1313D07} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
{9088E4E4-B855-457F-AE9E-D86709A5E1F4} = {F563ABB6-85FB-4CFC-B0D2-1D5130E8246D}
{C57382BC-EE93-49D5-BC40-5C98AF8AA048} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
{0246AA77-1A27-4A67-874B-6EF6F99E414E} = {4AE0D35B-D97A-44D0-8392-C9240377DCCE}
{50F6820F-D058-4E68-9E15-801F893F514E} = {36A7DEB7-5F88-4BFB-B57E-79EEC9950E25}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

View File

@ -1,6 +1,5 @@
import { platform } from './Environment';
import { getAssemblyNameFromUrl } from './Platform/DotNet';
import { enableLiveReloading } from './LiveReloading';
import './Rendering/Renderer';
import './Services/Http';
import './Services/UriHelper';
@ -37,13 +36,6 @@ async function boot() {
// Start up the application
platform.callEntryPoint(entryPointAssemblyName, entryPointMethod, []);
// Enable live reloading only if there's a "reload" attribute on the <script> tag.
// In production, this should not be the case.
const reloadUri = thisScriptElem.getAttribute('reload');
if (reloadUri) {
enableLiveReloading(reloadUri);
}
}
function getRequiredBootScriptAttribute(elem: HTMLScriptElement, attributeName: string): string {

View File

@ -1,92 +0,0 @@
const pollIntervalMs = 500;
const maxPollDurationMs = 10 * 1000;
export function enableLiveReloading(endpointUri: string) {
new ReloadContext(endpointUri).start();
}
class ReloadContext {
private _websocketUri: string;
private _didConnect = false;
private _pollUntilConnectedThenReload = false;
private _stopPollingAtTime: Date | null = null;
constructor(endpointUri: string) {
this._websocketUri = toAbsoluteWebSocketUri(endpointUri);
}
start() {
if (typeof WebSocket !== 'undefined') {
this._attemptToConnect(/* delay */ 0);
} else {
console.log('Browser does not support WebSocket, so live reloading will be disabled.');
}
}
private _attemptToConnect(delayMs: number) {
setTimeout(() => {
const source = new WebSocket(this._websocketUri);
source.onopen = event => this._onOpen();
source.onmessage = event => this._onMessage(event.data);
source.onerror = event => this._onError();
source.onclose = event => this._onClose();
}, delayMs);
}
private _onOpen() {
this._didConnect = true;
if (this._pollUntilConnectedThenReload) {
reloadNow();
}
}
private _onMessage(data: string) {
if (data === 'reload') {
reloadNow();
}
}
private _onClose() {
if (this._didConnect) {
// Looks like the server is being recycled (or possibly just shut down, but in the
// absence of a graceful shutdown we have no way to tell the difference)
// Wait until the server appears to be back, then reload
this._pollUntilConnectedThenReload = true;
this._stopPollingAtTime = new Date(new Date().valueOf() + maxPollDurationMs);
this._attemptToConnect(/* delay */ pollIntervalMs);
}
}
private _onError() {
if (!this._didConnect) {
if (this._pollUntilConnectedThenReload) {
if (new Date() < this._stopPollingAtTime!) {
// Continue polling
this._attemptToConnect(/* delay */ pollIntervalMs);
}
} else {
console.error(`The client app was compiled with live reloading enabled, but could not open `
+ ` a WebSocket connection to the server at ${this._websocketUri}\n`
+ `To fix this inconsistency, either run the server in development mode, or compile the `
+ `client app in Release configuration.`);
}
}
}
}
function toAbsoluteWebSocketUri(uri: string) {
const baseUri = document.baseURI;
if (baseUri) {
const lastSlashPos = baseUri.lastIndexOf('/');
const prefix = baseUri.substr(0, lastSlashPos);
uri = prefix + uri;
}
// Scheme must be ws: or wss:
return uri.replace(/^http/, 'ws');
}
function reloadNow() {
location.reload();
}

View File

@ -26,10 +26,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
"Adds a <link rel=stylesheet> tag with the specified 'href' value",
CommandOptionType.MultipleValue);
var reloadUri = command.Option("--reload-uri",
"If specified, enables live reloading and specifies the URI of the notification endpoint.",
CommandOptionType.SingleValue);
var outputPath = command.Option("--output",
"Path to the output file",
CommandOptionType.SingleValue);
@ -59,7 +55,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Cli.Commands
jsReferences.Values.ToArray(),
cssReferences.Values.ToArray(),
linkerEnabledFlag.HasValue(),
reloadUri.Value(),
outputPath.Value());
return 0;
}

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Blazor.Build
IEnumerable<string> jsReferences,
IEnumerable<string> cssReferences,
bool linkerEnabled,
string reloadUri,
string outputPath)
{
var template = GetTemplate(path);
@ -32,7 +31,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
}
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var entryPoint = GetAssemblyEntryPoint(assemblyPath);
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, assemblyReferences, jsReferences, cssReferences, linkerEnabled, reloadUri);
var updatedContent = GetIndexHtmlContents(template, assemblyName, entryPoint, assemblyReferences, jsReferences, cssReferences, linkerEnabled);
var normalizedOutputPath = Normalize(outputPath);
Console.WriteLine("Writing index to: " + normalizedOutputPath);
File.WriteAllText(normalizedOutputPath, updatedContent);
@ -104,8 +103,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
IEnumerable<string> assemblyReferences,
IEnumerable<string> jsReferences,
IEnumerable<string> cssReferences,
bool linkerEnabled,
string reloadUri)
bool linkerEnabled)
{
var resultBuilder = new StringBuilder();
@ -146,8 +144,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
assemblyEntryPoint,
assemblyReferences,
linkerEnabled,
tag.Attributes,
reloadUri);
tag.Attributes);
// Emit tags to reference any specified JS/CSS files
AppendReferenceTags(
@ -209,8 +206,7 @@ namespace Microsoft.AspNetCore.Blazor.Build
string assemblyEntryPoint,
IEnumerable<string> binFiles,
bool linkerEnabled,
List<KeyValuePair<string, string>> attributes,
string reloadUri)
List<KeyValuePair<string, string>> attributes)
{
var assemblyNameWithExtension = $"{assemblyName}.dll";
@ -232,11 +228,6 @@ namespace Microsoft.AspNetCore.Blazor.Build
attributesDict.Remove("linker-enabled");
}
if (!string.IsNullOrEmpty(reloadUri))
{
attributesDict["reload"] = reloadUri;
}
resultBuilder.Append("<script");
foreach (var attributePair in attributesDict)
{

View File

@ -4,16 +4,7 @@
<PropertyGroup>
<DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes>
<!-- By default, live reloading is on for debug builds -->
<UseBlazorLiveReloading Condition="'$(Configuration)' == 'Debug'">true</UseBlazorLiveReloading>
<BlazorLiveReloadUri>/_reload</BlazorLiveReloadUri>
<!-- We can remove this after updating to newer Razor tooling, where it's enabled by default -->
<UseRazorBuildServer>true</UseRazorBuildServer>
</PropertyGroup>
<ItemGroup>
<!-- In case you're using 'dotnet watch', enable reloading when editing .cshtml files -->
<Watch Include="$(ProjectDir)**\*.cshtml" />
</ItemGroup>
</Project>

View File

@ -23,7 +23,6 @@
</PropertyGroup>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(UseBlazorLiveReloading)'=='true'" Lines="reload:$(BlazorLiveReloadUri)" Overwrite="false" Encoding="Unicode"/>
<ItemGroup>
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

View File

@ -22,7 +22,6 @@
<BlazorWebRootName>wwwroot/</BlazorWebRootName>
<BlazorIndexHtmlName>Index.html</BlazorIndexHtmlName>
<BlazorOutputIndexHtmlName>$(BlazorIndexHtmlName.ToLowerInvariant())</BlazorOutputIndexHtmlName>
<BlazorBuildCompletedSignalPath>$(BaseBlazorDistPath)__blazorBuildCompleted</BlazorBuildCompletedSignalPath>
</PropertyGroup>
<ItemGroup>

View File

@ -23,9 +23,6 @@
<ItemGroup>
<FileWrites Include="@(BlazorItemOutput->'%(TargetOutputPath)')" />
</ItemGroup>
<PropertyGroup>
<_BlazorDidCopyFilesToOutputDirectory>true</_BlazorDidCopyFilesToOutputDirectory>
</PropertyGroup>
</Target>
<Target Name="_BlazorTrackResolveReferencesDidRun" AfterTargets="ResolveReferences">
@ -50,21 +47,6 @@
<Message Importance="$(_BlazorStatisticsReportImportance)" Text="%(_BlazorStatisticsOutput.Identity)" />
</Target>
<!--
We only issue the reload notification from here if you're *not* building inside VS.
If you are building inside VS, then it's possible you're building an arbitrary collection
of projects of which this is just one, and it's important to wait until all projects in
the build are completed before reloading, so the notification is instead triggered by the
VS extension that can see when a complete build process is finished.
-->
<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)" />
</Target>
<!-- Preparing blazor files for output:
PrepareBlazorOutputs
_PrepareBlazorOutputConfiguration
@ -83,7 +65,6 @@
_TouchBlazorApplicationAssemblies
_GenerateBlazorIndexHtml
_BlazorCopyFilesToOutputDirectory
_BlazorIssueLiveReloadNotification
The process for doing builds goes as follows:
Produce a hash file with the Hash SDK task and write that hash to a marker file.
@ -590,7 +571,6 @@
<BlazorIndexHtmlInput Include="@(BlazorPackageJsRef->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(BlazorPackageCssRef->'%(FullPath)')" />
<BlazorIndexHtmlInput Include="@(_BlazorLinkingOption)" />
<BlazorIndexHtmlInput Include="$(BlazorLiveReloadUri)" Condition="'$(UseBlazorLiveReloading)'=='true'" />
</ItemGroup>
<WriteLinesToFile
@ -617,10 +597,9 @@
</ItemGroup>
<PropertyGroup>
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
<_LiveReloadArg Condition="'$(UseBlazorLiveReloading)' == 'true' AND '$(BlazorLiveReloadUri)' != ''">--reload-uri "$(BlazorLiveReloadUri)"</_LiveReloadArg>
</PropertyGroup>
<Exec Command="$(BlazorBuildExe) build @(IntermediateAssembly) --html-page &quot;$(BlazorIndexHtml)&quot; @(_AppReferences->'--reference &quot;%(Identity)&quot;', ' ') @(_JsReferences->'--js &quot;%(Identity)&quot;', ' ') @(_CssReferences->'--css &quot;%(Identity)&quot;', ' ') $(_LinkerEnabledFlag) $(_LiveReloadArg) --output &quot;$(BlazorIndexHtmlOutputPath)&quot;" />
<Exec Command="$(BlazorBuildExe) build @(IntermediateAssembly) --html-page &quot;$(BlazorIndexHtml)&quot; @(_AppReferences->'--reference &quot;%(Identity)&quot;', ' ') @(_JsReferences->'--js &quot;%(Identity)&quot;', ' ') @(_CssReferences->'--css &quot;%(Identity)&quot;', ' ') $(_LinkerEnabledFlag) --output &quot;$(BlazorIndexHtmlOutputPath)&quot;" />
<ItemGroup Condition="Exists('$(BlazorIndexHtmlOutputPath)')">
<_BlazorIndex Include="$(BlazorIndexHtmlOutputPath)" />

View File

@ -64,15 +64,6 @@ namespace Microsoft.AspNetCore.Builder
});
}
// Definitely don't open a listener for live reloading in production, even if the
// client app was compiled with live reloading enabled
if (env.IsDevelopment())
{
// Whether or not live reloading is actually enabled depends on the client config
// For release builds, it won't be (by default)
applicationBuilder.UseBlazorLiveReloading(config);
}
// Finally, use SPA fallback routing (serve default page for anything else,
// excluding /_framework/*)
applicationBuilder.MapWhen(IsNotFrameworkDir, childAppBuilder =>

View File

@ -12,7 +12,6 @@ namespace Microsoft.AspNetCore.Blazor.Server
public string SourceMSBuildPath { get; }
public string SourceOutputAssemblyPath { get; }
public string WebRootPath { get; }
public string ReloadUri { get; }
public string DistPath
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");
@ -42,16 +41,6 @@ namespace Microsoft.AspNetCore.Blazor.Server
{
WebRootPath = webRootPath;
}
const string reloadMarker = "reload:";
var reloadUri = configLines
.Where(line => line.StartsWith(reloadMarker, StringComparison.Ordinal))
.Select(line => line.Substring(reloadMarker.Length))
.FirstOrDefault();
if (!string.IsNullOrEmpty(reloadUri))
{
ReloadUri = reloadUri;
}
}
}
}

View File

@ -1,140 +0,0 @@
// 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.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Server
{
internal class LiveReloadingContext
{
// Keep in sync with $(BlazorBuildCompletedSignalPath) in Blazor.MonoRuntime.props
private const string BlazorBuildCompletedSignalFile = "__blazorBuildCompleted";
// If some external automated process is writing multiple files to wwwroot,
// you probably want to wait until they've all been written before reloading.
// Pausing by 500 milliseconds is a crude effort - we might need a different
// mechanism (e.g., waiting until writes have stopped by 500ms).
private const int WebRootUpdateDelayMilliseconds = 500;
private static byte[] _reloadMessage = Encoding.UTF8.GetBytes("reload");
// If we don't hold references to them, then on Linux they get disposed.
// This static would leak memory if you called UseBlazorLiveReloading continually
// throughout the app lifetime, but the intended usage is just during init.
private static readonly List<FileSystemWatcher> _pinnedWatchers = new List<FileSystemWatcher>();
private readonly object _currentReloadListenerLock = new object();
private CancellationTokenSource _currentReloadListener
= new CancellationTokenSource();
public void Attach(IApplicationBuilder applicationBuilder, BlazorConfig config)
{
CreateFileSystemWatchers(config);
AddWebSocketsEndpoint(applicationBuilder, config.ReloadUri);
}
private void AddWebSocketsEndpoint(IApplicationBuilder applicationBuilder, string url)
{
applicationBuilder.UseWebSockets();
applicationBuilder.Use((context, next) =>
{
if (!string.Equals(context.Request.Path, url, StringComparison.Ordinal))
{
return next();
}
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync("This endpoint only accepts WebSockets connections.");
}
return HandleWebSocketRequest(
context.WebSockets.AcceptWebSocketAsync(),
context.RequestAborted);
});
}
private async Task HandleWebSocketRequest(Task<WebSocket> webSocketTask, CancellationToken requestAbortedToken)
{
var webSocket = await webSocketTask;
var reloadToken = _currentReloadListener.Token;
// Wait until either we get a signal to trigger a reload, or the client disconnects
// In either case we're done after that. It's the client's job to reload and start
// a new live reloading context.
try
{
var reloadOrRequestAbortedToken = CancellationTokenSource
.CreateLinkedTokenSource(reloadToken, requestAbortedToken)
.Token;
await Task.Delay(-1, reloadOrRequestAbortedToken);
}
catch (TaskCanceledException)
{
if (reloadToken.IsCancellationRequested)
{
await webSocket.SendAsync(
_reloadMessage,
WebSocketMessageType.Text,
true,
requestAbortedToken);
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Requested reload",
requestAbortedToken);
}
}
}
private void CreateFileSystemWatchers(BlazorConfig config)
{
// Watch for the "build completed" signal in the dist dir
var distFileWatcher = new FileSystemWatcher(config.DistPath);
distFileWatcher.Deleted += (sender, eventArgs) => {
if (eventArgs.Name.Equals(BlazorBuildCompletedSignalFile, StringComparison.Ordinal))
{
RequestReload(0);
}
};
distFileWatcher.EnableRaisingEvents = true;
_pinnedWatchers.Add(distFileWatcher);
// If there's a WebRootPath, watch for any file modification there
// WebRootPath is only used in dev builds, where we want to serve from wwwroot directly
// without requiring the developer to rebuild after changing the static files there.
// In production there's no need for it.
if (!string.IsNullOrEmpty(config.WebRootPath))
{
var webRootWatcher = new FileSystemWatcher(config.WebRootPath);
webRootWatcher.Deleted += (sender, evtArgs) => RequestReload(WebRootUpdateDelayMilliseconds);
webRootWatcher.Created += (sender, evtArgs) => RequestReload(WebRootUpdateDelayMilliseconds);
webRootWatcher.Changed += (sender, evtArgs) => RequestReload(WebRootUpdateDelayMilliseconds);
webRootWatcher.Renamed += (sender, evtArgs) => RequestReload(WebRootUpdateDelayMilliseconds);
webRootWatcher.EnableRaisingEvents = true;
_pinnedWatchers.Add(webRootWatcher);
}
}
private void RequestReload(int delayMilliseconds)
{
Task.Delay(delayMilliseconds).ContinueWith(_ =>
{
lock (_currentReloadListenerLock)
{
// Lock just to be sure two threads don't assign different new CTSs, of which
// only one would later get cancelled.
_currentReloadListener.Cancel();
_currentReloadListener = new CancellationTokenSource();
}
});
}
}
}

View File

@ -1,21 +0,0 @@
// 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.AspNetCore.Builder;
namespace Microsoft.AspNetCore.Blazor.Server
{
internal static class LiveReloadingExtensions
{
public static void UseBlazorLiveReloading(
this IApplicationBuilder applicationBuilder,
BlazorConfig config)
{
if (!string.IsNullOrEmpty(config.ReloadUri))
{
var context = new LiveReloadingContext();
context.Attach(applicationBuilder, config);
}
}
}
}

View File

@ -7,7 +7,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="2.0.0" />
</ItemGroup>

View File

@ -36,8 +36,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
assemblyReferences,
jsReferences,
cssReferences,
linkerEnabled: true,
reloadUri: "/my/reload");
linkerEnabled: true);
// Act & Assert: Start and end is not modified (including formatting)
Assert.StartsWith(htmlTemplatePrefix, instance);
@ -54,7 +53,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
Assert.Equal("MyApp.Entrypoint.dll", scriptElem.GetAttribute("main"));
Assert.Equal("MyNamespace.MyType::MyMethod", scriptElem.GetAttribute("entrypoint"));
Assert.Equal("System.Abc.dll,MyApp.ClassLib.dll", scriptElem.GetAttribute("references"));
Assert.Equal("/my/reload", scriptElem.GetAttribute("reload"));
Assert.False(scriptElem.HasAttribute("type"));
Assert.Equal(string.Empty, scriptElem.Attributes["custom1"].Value);
Assert.Equal("value", scriptElem.Attributes["custom2"].Value);
@ -70,30 +68,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
linkElems.Select(tag => tag.GetAttribute("href")),
cssReferences);
}
[Fact]
public void OmitsReloadAttributeIfNoReloadUriSpecified()
{
// Arrange
var htmlTemplate = "<script type='blazor-boot'></script>";
var assemblyReferences = new string[] { "System.Abc.dll", "MyApp.ClassLib.dll", };
// Act
var fileContents = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate,
"MyApp.Entrypoint",
"MyNamespace.MyType::MyMethod",
assemblyReferences,
/* js references */ new string[] {},
/* js references */ new string[] {},
/* linkerEnabled */ true,
/* reloadUri */ null);
// Assert
var parsedHtml = new HtmlParser().Parse(fileContents);
var scriptElem = parsedHtml.QuerySelector("script");
Assert.False(scriptElem.HasAttribute("reload"));
}
[Fact]
public void SuppliesHtmlTemplateUnchangedIfNoBootScriptPresent()
@ -105,7 +79,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
var cssReferences = new string[] { "my/styles.css" };
var content = IndexHtmlWriter.GetIndexHtmlContents(
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", assemblyReferences, jsReferences, cssReferences, linkerEnabled: true, reloadUri: "/my/reload");
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", assemblyReferences, jsReferences, cssReferences, linkerEnabled: true);
// Assert
Assert.Equal(htmlTemplate, content);

View File

@ -24,7 +24,6 @@
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Cli\Microsoft.AspNetCore.Blazor.Cli.csproj" />
<ProjectReference Include="..\testapps\BasicTestApp\BasicTestApp.csproj" />
<ProjectReference Include="..\testapps\LiveReloadTestApp\LiveReloadTestApp.csproj" />
<ProjectReference Include="..\testapps\TestServer\TestServer.csproj" />
</ItemGroup>

View File

@ -1,118 +0,0 @@
// 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 LiveReloadTestApp;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Diagnostics;
using System.IO;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
// We need an entirely separate test app for the live reloading tests, because
// otherwise it might break other tests that were running parallel (e.g., if we
// triggered a reload here while other tests were waiting for something to happen).
public class LiveReloadingTest
: ServerTestBase<DevHostServerFixture<LiveReloadTestApp.Program>>
{
private const string ServerPathBase = "/live/reloading/subdir";
private readonly DevHostServerFixture<Program> _serverFixture;
public LiveReloadingTest(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
: base(browserFixture, serverFixture)
{
_serverFixture = serverFixture;
serverFixture.Environment = "Development"; // Otherwise the server won't accept live reloading connections
serverFixture.PathBase = ServerPathBase;
Navigate(ServerPathBase);
WaitUntilLoaded();
}
[Fact]
public void ReloadsWhenWebRootFilesAreModified()
{
// Verify we have the expected starting point
var jsFileOutputSelector = By.Id("some-js-file-output");
Assert.Equal("initial value", Browser.FindElement(jsFileOutputSelector).Text);
var jsFilePath = Path.Combine(_serverFixture.ContentRoot, "wwwroot", "someJsFile.js");
var origContents = File.ReadAllText(jsFilePath);
try
{
// Edit the source file on disk
var newContents = origContents.Replace("'initial value'", "'modified value'");
File.WriteAllText(jsFilePath, newContents);
// See that the page reloads and reflects the updated source file
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(jsFileOutputSelector).Text == "modified value");
WaitUntilLoaded();
}
finally
{
// Restore original state
File.WriteAllText(jsFilePath, origContents);
}
}
[Fact]
public void ReloadsWhenBlazorAppRebuilds()
{
// Verify we have the expected starting point
var appElementSelector = By.TagName("app");
Assert.Equal("Hello, world!", Browser.FindElement(appElementSelector).Text);
var cshtmlFilePath = Path.Combine(_serverFixture.ContentRoot, "Home.cshtml");
var origContents = File.ReadAllText(cshtmlFilePath);
try
{
// Edit the source file on disk
var newContents = origContents.Replace("Hello", "Goodbye");
File.WriteAllText(cshtmlFilePath, newContents);
// Trigger build
var buildConfiguration = DetectBuildConfiguration(_serverFixture.ContentRoot);
var buildProcess = Process.Start(new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"build --no-restore --no-dependencies -c {buildConfiguration}",
WorkingDirectory = _serverFixture.ContentRoot
});
Assert.True(buildProcess.WaitForExit(60 * 1000));
Assert.Equal(0, buildProcess.ExitCode);
// See that the page reloads and reflects the updated source file
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(appElementSelector).Text == "Goodbye, world!");
}
finally
{
// Restore original state
File.WriteAllText(cshtmlFilePath, origContents);
}
}
private object DetectBuildConfiguration(string contentRoot)
{
// We want the test to issue the build with the same configuration that
// the project was already built with (otherwise there will be errors because
// of having multiple directories under /bin, plus it means we don't need
// to restore and rebuild all dependencies so it's faster)
var binDirInfo = new DirectoryInfo(Path.Combine(contentRoot, "bin"));
var configurationDirs = binDirInfo.GetDirectories();
Assert.Single(configurationDirs);
return configurationDirs[0].Name;
}
private void WaitUntilLoaded()
{
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(By.TagName("app")).Text != "Loading...");
}
}
}

View File

@ -1 +0,0 @@
<h1>Hello, world!</h1>

View File

@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<!-- Local alternative to <RunArguments>blazor serve</RunArguments> -->
<RunCommand>dotnet</RunCommand>
<RunArguments>run --project ..\..\..\src\Microsoft.AspNetCore.Blazor.Cli serve --pathbase /live/reloading/subdir</RunArguments>
<!-- For CI, force live reloading on even though it builds in release mode -->
<UseBlazorLiveReloading>true</UseBlazorLiveReloading>
</PropertyGroup>
<!-- Local alternative to <PackageReference Include="Microsoft.AspNetCore.Blazor.Build" /> -->
<Import Project="..\..\..\src\Microsoft.AspNetCore.Blazor.Build\ReferenceFromSource.props" />
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Blazor.Browser\Microsoft.AspNetCore.Blazor.Browser.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj" />
</ItemGroup>
</Project>

View File

@ -1,15 +0,0 @@
// 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.AspNetCore.Blazor.Browser.Rendering;
namespace LiveReloadTestApp
{
public class Program
{
static void Main(string[] args)
{
new BrowserRenderer().AddComponent<Home>("app");
}
}
}

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Live reload test app</title>
<base href="/live/reloading/subdir/" />
</head>
<body>
<app>Loading...</app>
<div id="some-js-file-output"></div>
<script type="blazor-boot"></script>
<script>
// The test about live reloading should be independent of any
// dev server caching headers
document.write("<script src='someJsFile.js?nocache=" + (new Date().valueOf()) + "'></" + "script>");
</script>
</body>
</html>

View File

@ -1,3 +0,0 @@
// We modify this on disk during E2E tests to verify it causes a reload
var valueToWrite = 'initial value';
document.getElementById('some-js-file-output').textContent = valueToWrite;

View File

@ -4,7 +4,6 @@
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.BlazorExtension
{
@ -12,24 +11,8 @@ 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

@ -1,119 +0,0 @@
// 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 useLiveReloading = project.GetPropertyValue("UseBlazorLiveReloading");
var projectDir = project.GetPropertyValue("ProjectDir");
var outputPath = project.GetPropertyValue("OutputPath");
var signalFilePath = project.GetPropertyValue("BlazorBuildCompletedSignalPath");
if (string.Equals(useLiveReloading, "true", StringComparison.Ordinal)
&& !string.IsNullOrEmpty(projectDir)
&& !string.IsNullOrEmpty(outputPath)
&& !string.IsNullOrEmpty(signalFilePath))
{
var fullPath = Path.Combine(
projectDir,
outputPath,
signalFilePath);
_signalFilePathsToNotify.Add(fullPath);
}
}
});
}
return VSConstants.S_OK;
}
}
}

View File

@ -4,7 +4,6 @@
<MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<UseCodebase>true</UseCodebase>
<!-- Don't import the directory props and targets, they aren't compatible with an old-style csproj -->
<ImportDirectoryBuildProps>false</ImportDirectoryBuildProps>
<ImportDirectoryBuildTargets>false</ImportDirectoryBuildTargets>
@ -164,7 +163,6 @@
<Visible>false</Visible>
</Content>
</ItemGroup>
<!--
We need to generate the assembly attributes for our assembly using the version from the build, so
we can flow it to the about dialog.
@ -174,49 +172,27 @@
<_Parameter1>$(VersionPrefix)-$(VersionSuffix)</_Parameter1>
</_VSIXAssemblyAttribute>
</ItemGroup>
<Target
Name="_GenerateVSIXAssemblyAttributesHash"
DependsOnTargets="PrepareForBuild"
Condition="'@(_VSIXAssemblyAttribute)' != ''">
<Target Name="_GenerateVSIXAssemblyAttributesHash" DependsOnTargets="PrepareForBuild" Condition="'@(_VSIXAssemblyAttribute)' != ''">
<!-- We only use up to _Parameter1 for most attributes, but other targets may add additional assembly attributes with multiple parameters. -->
<Hash ItemsToHash="@(_VSIXAssemblyAttribute->'%(Identity)%(_Parameter1)%(_Parameter2)%(_Parameter3)%(_Parameter4)%(_Parameter5)%(_Parameter6)%(_Parameter7)%(_Parameter8)')">
<Output TaskParameter="HashResult" PropertyName="_VSIXAssemblyAttributesHash" />
</Hash>
<WriteLinesToFile
Lines="$(_VSIXAssemblyAttributesHash)"
File="$(_GeneratedVSIXAssemblyInfoInputsCacheFile)"
Overwrite="True"
WriteOnlyWhenDifferent="True" />
<WriteLinesToFile Lines="$(_VSIXAssemblyAttributesHash)" File="$(_GeneratedVSIXAssemblyInfoInputsCacheFile)" Overwrite="True" WriteOnlyWhenDifferent="True" />
<ItemGroup>
<FileWrites Include="$(_GeneratedVSIXAssemblyInfoInputsCacheFile)" />
</ItemGroup>
</Target>
<Target
Name="_GenerateVSIXAssemblyAttributes"
DependsOnTargets="_GenerateVSIXAssemblyAttributesHash"
Inputs="$(_GeneratedVSIXAssemblyInfoInputsCacheFile)"
Outputs="$(_GeneratedVSIXAssemblyInfoFile)"
BeforeTargets="CoreCompile">
<Target Name="_GenerateVSIXAssemblyAttributes" DependsOnTargets="_GenerateVSIXAssemblyAttributesHash" Inputs="$(_GeneratedVSIXAssemblyInfoInputsCacheFile)" Outputs="$(_GeneratedVSIXAssemblyInfoFile)" BeforeTargets="CoreCompile">
<ItemGroup>
<Compile Include="$(_GeneratedVSIXAssemblyInfoFile)">
<Visible>false</Visible>
</Compile>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="@(_VSIXAssemblyAttribute)" Language="C#" OutputFile="$(_GeneratedVSIXAssemblyInfoFile)" />
<ItemGroup>
<FileWrites Include="$(_GeneratedVSIXAssemblyInfoFile)" />
</ItemGroup>
</Target>
<!--
END INTERESTING STUFF
@ -275,7 +251,6 @@
<ItemGroup>
<Compile Include="AboutDialogInfoAttribute.cs" />
<Compile Include="BlazorPackage.cs" />
<Compile Include="LiveReloadBuildWatcher.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@ -334,7 +309,6 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- Must be defined after the CSharp.targets -->
<PropertyGroup>
<_GeneratedVSIXAssemblyInfoInputsCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).VSIXAssemblyInfo.cache.txt</_GeneratedVSIXAssemblyInfoInputsCacheFile>